你知道 Java 类是如何被加载的吗?

一:前言

最近给一个非Java方向的朋友讲了下双亲委派模型,朋友让我写篇文章深度研究下JVM的ClassLoader,我确实也好久没写JVM相关的文章了,有点手痒痒,涂了皮炎平也抑制不住。

我在向朋友解释的时候是这么说的:双亲委派模型中,ClassLoader在加载类的时候,会先交由它的父ClassLoader加载,只有当父ClassLoader加载失败的情况下,才会尝试自己去加载。这样可以实现部分类的复用,又可以实现部分类的隔离,因为不同ClassLoader加载的类是互相隔离的。

不过贸然的向别人解释双亲委派模型是不妥的,如果在不了解JVM的类加载机制的情况下,又如何能很好的理解“不同ClassLoader加载的类是互相隔离的”这句话呢?所以为了理解双亲委派,最好的方式,就是先了解下ClassLoader的加载流程。

二:Java 类是如何被加载的

2.1:何时加载类
我们首先要清楚的是,Java类何时会被加载?

《深入理解Java虚拟机》给出的答案是:

1:遇到new、getstatic、putstatic 等指令时。
2:对类进行反射调用的时候。
3:初始化某个类的子类的时候。
4:虚拟机启动时会先加载设置的程序主类。
5:使用JDK 1.7 的动态语言支持的时候。
其实要我说,最通俗易懂的答案就是:当运行过程中需要这个类的时候。

那么我们不妨就从如何加载类开始说起。

2.2:怎么加载类
利用ClassLoader加载类很简单,直接调用ClassLoder的loadClass()方法即可,我相信大家都会,但是还是要举个栗子:

public class Test {public static void main(String[] args) throws ClassNotFoundException {Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");}
}

上面这段代码便实现了让ClassLoader去加载 “com.wangxiandeng.test.Dog” 这个类,是不是 so easy。但是JDK 提供的 API 只是冰山一角,看似很简单的一个调用,其实隐藏了非常多的细节,我这个人吧,最喜欢做的就是去揭开 API 的封装,一探究竟。

2.3:JVM 是怎么加载类的
JVM 默认用于加载用户程序的ClassLoader为AppClassLoader,不过无论是什么ClassLoader,它的根父类都是java.lang.ClassLoader。在上面那个例子中,loadClass()方法最终会调用到ClassLoader.definClass1()中,这是一个 Native 方法。

static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,ProtectionDomain pd, String source); 

看到 Native 方法莫心慌,不要急,打开OpenJDK源码,我等继续走马观花便是!

definClass1()对应的 JNI 方法为 Java_java_lang_ClassLoader_defineClass1()

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,jclass cls,jobject loader,jstring name,jbyteArray data,jint offset,jint length,jobject pd,jstring source)
{......result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);......return result;
}

Java_java_lang_ClassLoader_defineClass1 主要是调用了JVM_DefineClassWithSource()加载类,跟着源码往下走,会发现最终调用的是 jvm.cpp 中的 jvm_define_class_common()方法。

static jclass jvm_define_class_common(JNIEnv *env, const char *name,jobject loader, const jbyte *buf,jsize len, jobject pd, const char *source,TRAPS) {......ClassFileStream st((u1*)buf, len, source, ClassFileStream::verify);Handle class_loader (THREAD, JNIHandles::resolve(loader));if (UsePerfData) {is_lock_held_by_thread(class_loader,ClassLoader::sync_JVMDefineClassLockFreeCounter(),THREAD);}Handle protection_domain (THREAD, JNIHandles::resolve(pd));Klass* k = SystemDictionary::resolve_from_stream(class_name,class_loader,protection_domain,&st,CHECK_NULL);......return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

上面这段逻辑主要就是利用 ClassFileStream 将要加载的class文件转成文件流,然后调用SystemDictionary::resolve_from_stream(),生成 Class 在 JVM 中的代表:Klass。对于Klass,大家可能不太熟悉,但是在这里必须得了解下。说白了,它就是JVM 用来定义一个Java Class 的数据结构。不过Klass只是一个基类,Java Class 真正的数据结构定义在 InstanceKlass中。

class InstanceKlass: public Klass {protected:Annotations*    _annotations;......ConstantPool* _constants;......Array<jushort>* _inner_classes;......Array<Method*>* _methods;Array<Method*>* _default_methods;......Array<u2>*      _fields;
}

可见 InstanceKlass 中记录了一个 Java 类的所有属性,包括注解、方法、字段、内部类、常量池等信息。这些信息本来被记录在Class文件中,所以说,InstanceKlass就是一个Java Class 文件被加载到内存后的形式。

再回到上面的类加载流程中,这里调用了 SystemDictionary::resolve_from_stream(),将 Class 文件加载成内存中的 Klass。

resolve_from_stream() 便是重中之重!主要逻辑有下面几步:

1:判断是否允许并行加载类,并根据判断结果进行加锁。

bool DoObjectLock = true;
if (is_parallelCapable(class_loader)) {DoObjectLock = false;
}
ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL);
Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
check_loader_lock_contention(lockObject, THREAD);
ObjectLocker ol(lockObject, THREAD, DoObjectLock);

如果允许并行加载,则不会对ClassLoader进行加锁,只对SystemDictionary加锁。否则,便会利用 ObjectLocker 对ClassLoader 加锁,保证同一个ClassLoader在同一时刻只能加载一个类。ObjectLocker 会在其构造函数中获取锁,并在析构函数中释放锁。

允许并行加载的好处便是精细化了锁粒度,这样可以在同一时刻加载多个Class文件。

2:解析文件流,生成 InstanceKlass。

InstanceKlass* k = NULL;k = KlassFactory::create_from_stream(st,class_name,loader_data,protection_domain,NULL, // host_klassNULL, // cp_patchesCHECK_NULL);

3:利用SystemDictionary注册生成的 Klass。

SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDictionary并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个ClassLoaderData 中都保存着一个私有的 Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。

我们来看看注册的代码:

if (is_parallelCapable(class_loader)) {InstanceKlass* defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);if (!HAS_PENDING_EXCEPTION && defined_k != k) {// If a parallel capable class loader already defined this class, register 'k' for cleanup.assert(defined_k != NULL, "Should have a klass if there's no exception");loader_data->add_to_deallocate_list(k);k = defined_k;}
} else {define_instance_class(k, THREAD);
}

如果允许并行加载,那么前面就不会对ClassLoader加锁,所以在同一时刻,可能对同一Class文件加载了多次。但是同一Class在同一ClassLoader中必须保持唯一性,所以这里会先利用 SystemDictionary 查询 ClassLoader 是否已经加载过相同 Class。

如果已经加载过,那么就将当前线程刚刚加载的InstanceKlass加入待回收列表,并将 InstanceKlass* k 重新指向利用SystemDictionary查询到的 InstanceKlass。
如果没有查询到,那么就将刚刚加载的 InstanceKlass 注册到 ClassLoader的 Dictionary 中 中。
虽然并行加载不会锁住ClassLoader,但是会在注册 InstanceKlass 时对 SystemDictionary 加锁,所以不需要担心InstanceKlass 在注册时的并发操作。

如果禁止了并行加载,那么直接利用SystemDictionary将 InstanceKlass 注册到 ClassLoader的 Dictionary 中即可。

resolve_from_stream()的主要流程就是上面三步,很明显,最重要的是第二步,从文件流生成InstanceKlass。

生成InstanceKlass 调用的是 KlassFactory::create_from_stream()方法,它的主要逻辑就是下面这段代码。

ClassFileParser parser(stream,name,loader_data,protection_domain,host_klass,cp_patches,ClassFileParser::BROADCAST, // publicity levelCHECK_NULL);InstanceKlass* result = parser.create_instance_klass(old_stream != stream, CHECK_NULL);

原来 ClassFileParser 才是真正的主角啊!它才是将Class文件升华成InstanceKlass的幕后大佬!

2.4:不得不说的ClassFileParser
ClassFileParser 加载Class文件的入口便是 create_instance_klass()。顾名思义,用来创建InstanceKlass的。

create_instance_klass()主要就干了两件事:

(1):为 InstanceKlass 分配内存

InstanceKlass* const ik =InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);

(2):分析Class文件,填充 InstanceKlass 内存区域

fill_instance_klass(ik, changed_by_loadhook, CHECK_NULL);
我们先来说道说道第一件事,为 InstanceKlass 分配内存。

内存分配代码如下:

const int size = InstanceKlass::size(parser.vtable_size(),parser.itable_size(),nonstatic_oop_map_size(parser.total_oop_map_count()),parser.is_interface(),parser.is_anonymous(),should_store_fingerprint(parser.is_anonymous()));
ClassLoaderData* loader_data = parser.loader_data();
InstanceKlass* ik;
ik = new (loader_data, size, THREAD) InstanceKlass(parser, InstanceKlass::_misc_kind_other);

这里首先计算了InstanceKlass在内存中的大小,要知道,这个大小在Class 文件编译后就被确定了。

然后便 new 了一个新的 InstanceKlass 对象。这里并不是简单的在堆上分配内存,要注意的是Klass 对 new 操作符进行了重载:

void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {return Metaspace::allocate(loader_data, word_size, MetaspaceObj::ClassType, THREAD);
}

分配 InstanceKlass 的时候调用了 Metaspace::allocate():

                              MetaspaceObj::Type type, TRAPS) {......MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;......MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);......return result;
}

由此可见,InstanceKlass 是分配在 ClassLoader的 Metaspace(元空间) 的方法区中。从 JDK8 开始,HotSpot 就没有了永久代,类都分配在 Metaspace 中。Metaspace 和永久代不一样,采用的是 Native Memory,永久代由于受限于 MaxPermSize,所以当内存不够时会内存溢出。

分配完 InstanceKlass 内存后,便要着手第二件事,分析Class文件,填充 InstanceKlass 内存区域。

ClassFileParser 在构造的时候就会开始分析Class文件,所以fill_instance_klass()中只需要填充即可。填充结束后,还会调用 java_lang_Class::create_mirror()创建 InstanceKlass 在Java 层的 Class 对象。

void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loadhook, TRAPS) {.....ik->set_class_loader_data(_loader_data);ik->set_nonstatic_field_size(_field_info->nonstatic_field_size);ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields);ik->set_static_oop_field_count(_fac->count[STATIC_OOP]);ik->set_name(_class_name);......java_lang_Class::create_mirror(ik,Handle(THREAD, _loader_data->class_loader()),module_handle,_protection_domain,CHECK);
}

顺便提一句,对于Class文件结构不熟悉的同学,可以看下我两年前写的一篇文章:

《汪先生:Jvm之用java解析class文件》

到这儿,Class文件已经完成了华丽的转身,由冷冰冰的二进制文件,变成了内存中充满生命力的InstanceKlass。

三:再谈双亲委派

如果你耐心的看完了上面的源码分析,你一定对 “不同ClassLoader加载的类是互相隔离的” 这句话的理解又上了一个台阶。

我们总结下:每个ClassLoader都有一个 Dictionary 用来保存它所加载的InstanceKlass信息。并且,每个 ClassLoader 通过锁,保证了对于同一个Class,它只会注册一份 InstanceKlass 到自己的 Dictionary 。

正式由于上面这些原因,如果所有的 ClassLoader 都由自己去加载 Class 文件,就会导致对于同一个Class文件,存在多份InstanceKlass,所以即使是同一个Class文件,不同InstanceKlasss 衍生出来的实例类型也是不一样的。

举个栗子,我们自定义一个 ClassLoader,用来打破双亲委派模型:

public class CustomClassloader extends URLClassLoader {public CustomClassloader(URL[] urls) {super(urls);}@Overrideprotected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {if (name.startsWith("com.wangxiandeng")) {return findClass(name);}return super.loadClass(name, resolve);}
}

再尝试加载Studen类,并实例化:

public class Test {public static void main(String[] args) throws Exception {URL url[] = new URL[1];url[0] = Thread.currentThread().getContextClassLoader().getResource("");CustomClassloader customClassloader = new CustomClassloader(url);Class clazz = customClassloader.loadClass("com.wangxiandeng.Student");Student student = (Student) clazz.newInstance();}
}

运行后便会抛出类型强转异常:

Exception in thread "main" java.lang.ClassCastException:com.wangxiandeng.Student cannot be cast to com.wangxiandeng.Student

为什么呢?

因为实例化的Student对象所属的 InstanceKlass 是由CustomClassLoader加载生成的,而我们要强转的类型Student.Class 对应的 InstanceKlass 是由系统默认的ClassLoader生成的,所以本质上它们就是两个毫无关联的InstanceKlass,当然不能强转。

有同学问到:为什么“强转的类型Student.Class 对应的 InstanceKlass 是由系统默认的ClassLoader生成的”?
其实很简单,我们反编译下字节码:

  public static void main(java.lang.String[]) throws java.lang.Exception;descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=4, locals=5, args_size=10: iconst_11: anewarray     #2                  // class java/net/URL4: astore_15: aload_16: iconst_07: invokestatic  #3                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;10: invokevirtual #4                  // Method java/lang/Thread.getContextClassLoader:()Ljava/lang/ClassLoader;13: ldc           #5                  // String15: invokevirtual #6                  // Method java/lang/ClassLoader.getResource:(Ljava/lang/String;)Ljava/net/URL;18: aastore19: new           #7                  // class com/wangxiandeng/classloader/CustomClassloader22: dup23: aload_124: invokespecial #8                  // Method com/wangxiandeng/classloader/CustomClassloader."<init>":([Ljava/net/URL;)V27: astore_228: aload_229: ldc           #9                  // String com.wangxiandeng.Student31: invokevirtual #10                 // Method com/wangxiandeng/classloader/CustomClassloader.loadClass:(Ljava/lang/String;)Ljava/lang/Class;34: astore_335: aload_336: invokevirtual #11                 // Method java/lang/Class.newInstance:()Ljava/lang/Object;39: checkcast     #12                 // class com/wangxiandeng/Student42: astore        444: return

可以看到在利用加载的Class初始化实例后,调用了 checkcast 进行类型转化,checkcast 后的操作数 #12 即为Student这个类在常量池中的索引:

#12 = Class #52 // com/wangxiandeng/Student
下面我们可以看看 checkcast 在HotSpot中的实现。
HotSpot 目前有三种字节码执行引擎,目前采用的是模板解释器,可以看下我这篇文章:《汪先生:JVM之模板解释器》。
早期的HotSpot采用的是字节码解释器。模板解释器对于指令的执行都是用汇编写的,而字节码解释器采用的C++进行的翻译,为了看起来比较舒服,我们就不看汇编了,直接看字节码解释器就行了。如果你的汇编功底很好,当然也可以直接看模板解释器,我之前写的文章《汪先生:JVM之创建对象源码分析》这里就是分析模板解释器对于 new 指令的实现。

废话不多说,我们来看看字节码解释器对于checkcast的实现,代码在 bytecodeInterpreter.cpp 中

CASE(_checkcast):if (STACK_OBJECT(-1) != NULL) {VERIFY_OOP(STACK_OBJECT(-1));// 拿到 checkcast 指令后的操作数,本例子中即 Student.Class 在常量池中的索引:#12u2 index = Bytes::get_Java_u2(pc+1);// 如果常量池还没有解析,先进行解析,即将常量池中的符号引用替换成直接引用,//此时就会触发Student.Class 的加载if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);}// 获取上一步系统加载的Student.Class 对应的 InstanceKlassKlass* klassOf = (Klass*) METHOD->constants()->resolved_klass_at(index);// 获取要强转的对象的实际类型,即我们自己手动加载的Student.Class 对应的 InstanceKlassKlass* objKlass = STACK_OBJECT(-1)->klass(); // ebx// 现在就比较简单了,直接看看上面的两个InstanceKlass指针内容是否相同// 不同的情况下则判断是否存在继承关系if (objKlass != klassOf && !objKlass->is_subtype_of(klassOf)) {// Decrement counter at checkcast.BI_PROFILE_SUBTYPECHECK_FAILED(objKlass);ResourceMark rm(THREAD);char* message = SharedRuntime::generate_class_cast_message(objKlass, klassOf);VM_JAVA_ERROR(vmSymbols::java_lang_ClassCastException(), message, note_classCheck_trap);}// Profile checkcast with null_seen and receiver.BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/false, objKlass);} else {// Profile checkcast with null_seen and receiver.BI_PROFILE_UPDATE_CHECKCAST(/*null_seen=*/true, NULL);}

通过对上面代码的分析,我相信大家已经理解了 “强转的类型Student.Class 对应的 InstanceKlass 是由系统默认的ClassLoader生成的” 这句话了。

双亲委派的好处是尽量保证了同一个Class文件只会生成一个InstanceKlass,但是某些情况,我们就不得不去打破双亲委派了,比如我们想实现Class隔离的时候。

回复下箫陌同学的问题:

// 如果常量池还没有解析,先进行解析,即将常量池中的符号引用替换成直接引用,
//此时就会触发Student.Class 的加载
if (METHOD->constants()->tag_at(index).is_unresolved_klass()) {
CALL_VM(InterpreterRuntime::quicken_io_cc(THREAD), handle_exception);
}
请问,为何这里会重新加载Student.Class?jvm是不是有自己的class加载链路,然后系统循着链路去查找class是否已经被加载?那该怎么把自定义的CustomClassloader 加到这个查询链路中去呢?

第一种方法:设置启动参数 java -Djava.system.class.loader

第二种方法:利用Thread.setContextClassLoder

这里就有点技巧了,看下代码:

public class Test {public static void main(String[] args) throws Exception {URL url[] = new URL[1];url[0] = Thread.currentThread().getContextClassLoader().getResource("");final CustomClassloader customClassloader = new CustomClassloader(url);Thread.currentThread().setContextClassLoader(customClassloader);Class clazz = customClassloader.loadClass("com.wangxiandeng.ClassTest");Object object = clazz.newInstance();Method method = clazz.getDeclaredMethod("test");method.invoke(object);}
}
public class ClassTest {public void test() throws Exception{Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.wangxiandeng.Student");Student student = (Student) clazz.newInstance();System.out.print(student.getClass().getClassLoader());}
}

要注意的是在设置线程的ClassLoader后,并不是直接调用 new ClassTest().test()。为什么呢?因为直接强引用的话,会在解析Test.Class的常量池时,利用系统默认的ClassLoader加载了ClassTest,从而又触发了ClassTest.Class的解析。为了避免这种情况的发生,这里利用CustomClassLoader去加载ClassTest.Class,再利用反射机制调用test(),此时在解析ClassTest.Class的常量池时,就会利用CustomClassLoader去加载Class常量池项,也就不会发生异常了。

四:总结

写完这篇文章,手也不痒了,甚爽!这篇文章从双亲委派讲到了Class文件的加载,最后又绕回到双亲委派,看似有点绕,其实只有理解了Class的加载机制,才能更好的理解类似双亲委派这样的机制,否则只死记硬背一些空洞的理论,是无法起到由内而外的理解的。


原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

我们为什么需要 SpringBoot?

作者 | 阿文&#xff0c;责编 | 郭芮头图 | CSDN 下载自东方IC出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;任何先进技术的产生都不是凭空出现的&#xff0c;SpringBoot 也不例外&#xff0c;SpringBoot 是基于Spring 的基础上产生的。总所周知&#xff0c;Spri…

高德网络定位之“移动WiFi识别”

导读 随着时代的发展&#xff0c;近10年来位置产业蓬勃发展&#xff0c;定位能力逐渐从低精度走向高精度&#xff0c;从部分场景走向泛在定位。设备和场景的丰富&#xff0c;使得定位技术和能力也不断的优化更新。定位能力包括GNSS、DR&#xff08;航迹推算&#xff09;、MM&a…

JavaScript-字符串

字符串 正常字符串使用单引号 或者 双引号 包裹注意转义字符 \ \ \n \t \u4e2d \u#### Unicode字符 \x41 Ascll字符多行字符串编写 // tab 上面 esc下面的引号 let msg helloworld你好 console.log(msg)模板字符串 let name ht let age 5; let msg hello${name}你…

Alibaba Sentinel规则持久化-拉模式-手把手教程【基于文件】

文章目录一、拉模式架构二、原理简述三、编写3.1 加依赖3.2 写代码3.3 配置四、优缺点分析五、你可能会有的疑问六、参考文档七、案例测试7.1. 添加流控规则7.2. 服务停止7.3. 重新启动服务7.4. 调用接口7.5. 查看流控规则本文实现基于拉模式的Alibaba Sentinel规则持久化。一、…

开发者说:Seata 0.7.0 版本,你 get 'Metrics' 技能了吗?

从用户的视角来感受一个开源项目的成长&#xff0c;是我们推出「开发者说」专栏的初衷&#xff0c;即在开发者进行开源项目选型时&#xff0c;提供更为立体的项目信息。专栏所有内容均来自作者原创/投稿&#xff0c;本文是「开发者说」的第9篇&#xff0c;作者郑扬勇&#xff0…

面试时遇到「看门狗」脖子上挂着「时间轮」,我就问你怕不怕?

来源 | Why技术封图 | CSDN 下载于视觉中国之前写了一篇文章&#xff0c;有一个小节中写到这样一段话&#xff1a;于是就有读者来问了&#xff1a;老哥&#xff0c;看门狗介绍一下呗。面试的时候被问到了&#xff0c;没有回答上来。听到这个问题我脑海里首先浮现出了几个问题&…

海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解

前言 表格存储Tablestore是阿里云自研的面向海量结构化数据存储的Serverless NoSQL多模型数据库。Tablestore在阿里云官网上有各种文档介绍&#xff0c;也发布了很多场景案例文章&#xff0c;这些文章收录在这个合集中《表格存储Tablestore权威指南》。值得一提的是&#xff0…

JavaScript-数组

数组 Array可以包含任意的数据类型 var arr [1,2,3,4,5,6] arr[0] // 取值 arr[0] 3 // 赋值长度 arr.length arr.length 10 // 长度是可变的注意&#xff1a;假如给arr.length 赋值&#xff0c;数组大小就会发生变化。如果赋值过小&#xff0c;元素就会丢失 获取指定字…

Alibaba Sentinel规则持久化-推模式-手把手教程【基于Nacos】

前面&#xff0c;已经为Sentinel实现了 基于拉模式的规则持久化 &#xff0c;本文来实现基于 推模式的规则持久化。 文章目录一、推模式架构图二、原理简述三、微服务改造3.1. 加依赖3.2. 添加配置四、Sentinel控制台改造五、编译 & 启动六、测试测试1&#xff1a;测试2&am…

现代IM系统中的消息系统架构 - 实现篇

序 消息类场景是表格存储&#xff08;Tablestore&#xff09;主推的方向之一&#xff0c;因其数据存储结构在消息类数据存储上具有天然优势。为了方便用户基于Tablestore为消息类场景建模&#xff0c;Tablestore封装Timeline模型&#xff0c;旨在让用户更快捷的实现消息类场景…

linux 环境 RocketMQ 4.8.0 安装、部署控制台

windows下RocketMQ下载、安装、部署、控制台 https://gblfy.blog.csdn.net/article/details/115734482 文章目录一、软件下载二、启动2.1. Linux/Unix/MacOS安装教程2.2. 验证RocketMQ功能正常&#xff08;可选&#xff09;2.3. 服务停止三、安装可视化插件3.1. github下载3.2.…

数据洪流时代,开发者这样硬核突围!

随着社会信息化的脚步加快&#xff0c;我们每个人无时无刻都在产生数据&#xff1a;刷抖音、聊微信、视频会议、点外卖……拇指轻轻一点击的背后&#xff0c;将引发意想不到的数据洪流&#xff1a;据 IDC 发布《数据时代 2025》的报告显示&#xff0c;全球每年产生的数据将从 2…

机器学习在交通标志检测与精细分类中的应用

导读 数据对于地图来说十分重要&#xff0c;没有数据&#xff0c;就没有地图服务。用户在使用地图服务时&#xff0c;不太会想到数据就像冰山一样&#xff0c;用户可见只是最直接、最显性的产品功能部分&#xff0c;而支撑显性部分所需要的根基&#xff0c;往往更庞大。 地图…

JavaScript-分支和循环

if 判断 var age1 3; if (age1>5){console.log("da3") }else if (age1>2){console.log("da2") }else {console.log("else") }while循环 while (age1<20){age1 age1 1console.log(age1) } // do while 最少执行一次 do{age1 age1 …

Kubenetes 监控一站式解决方案:阿里云 Prometheus 免费公测

Prometheus是目前企业级云原生应用的首选开源监控工具。作为云原生计算基金会&#xff08;CNCF&#xff09;第二个毕业的项目&#xff08;第一个是Kubernetes&#xff09;&#xff0c;Prometheus对K8s容器环境有很好的原生支持。近日刚刚发布免费公测的阿里云Prometheus&#x…

SpringBoot2.x Nacos RocketMQ 事务消息

需求背景&#xff1a; 现在有内容中心&#xff08;content-center&#xff09;和 用户中心&#xff08;user-center&#xff09;2个微服务&#xff0c;请求内容中心&#xff0c;发送消息给用户中心&#xff0c;完成为指定用户添加积分操作。 文章目录一、准备工作1. 版本对照2.…

阿里云宣布3年再投2000亿

4月20日&#xff0c;阿里云宣布&#xff1a;未来3年再投2000亿&#xff0c;用于云操作系统、服务器、芯片、网络等重大核心技术研发攻坚和面向未来的数据中心建设。 近期&#xff0c;谷歌、美团等相继关闭或收缩云计算业务。在当前经济形势下&#xff0c;阿里云是否会缩减投入&…

JavaScript-Map和Set

ES6新特性 Map var map new Map([[wang, 23],[ht, 22],[test,[1,2,3,yy]],[3,test num]]) // 通过 key获取 value var test1 map.get(test); var num map.get(3); console.log(test1) console.log(num) // 添加新的 键值对 map.set(admin, 123456) console.log(map) // 修…

MaxCompute技术人背后的故事:从ApacheORC到AliORC

2019大数据技术公开课第一季《技术人生专访》来袭&#xff0c;本季将带领开发者们探讨大数据技术&#xff0c;分享不同国家的工作体验。本文整理自阿里巴巴计算平台事业部高级技术专家吴刚的专访&#xff0c;将为大家介绍Apache ORC开源项目、主流的开源列存格式ORC和Parquet的…

vue中设置height:100%无效的问题及解决方法

在vue.js中写新的components的时候&#xff0c;如果在新页面中的模板中设置height&#xff1a;100%的时候一直无效&#xff0c;在App.vue中&#xff1a;<template><div id"app"><router-view/></div> </template><script> expor…