何时加载类
遇到 new、getstatic、putstatic 等指令时。
对类进行反射调用的时候。
初始化某个类的子类的时候。
虚拟机启动时会先加载设置的程序主类。
使用 dynamic 动态语言支持等相关特性时。
从 Java 到 cpp 源码分析
JVM 默认用于加载用户程序的ClassLoader为AppClassLoader,不过无论是什么ClassLoader,它的根父类都是java.lang.ClassLoader。在上面那个例子中,loadClass()方法最终会调用到ClassLoader.definClass1()中,这是一个 Native 方法。
1 2 static native Class<?> defineClass1(ClassLoader loader, String name, byte [] b, int off, int len, ProtectionDomain pd, String source);
definClass1()对应的 JNI 方法为 Java_java_lang_ClassLoader_defineClass1()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 JNIEXPORT jclass JNICALLJava_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()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 ()); }
其上的步骤细分下来共三步:
将 class 文件转化为字节流。
求当前线程是否持有锁,并且显式地进入 protection_domain。
将字节流转化成 Klass 的实例(class 文件在 JVM 中的内存代表),注册进 SystemDictionary 里。
Klass 是 JVM 用来定义 Java Class 的数据结构。不过 Klass 只是一个基类,Java Class 真正的数据结构定义在 InstanceKlass 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class InstanceKlass : public Klass { protected : Annotations* _annotations; ...... ConstantPool* _constants; ...... Array<jushort>* _inner_classes; ...... Array<Method*>* _methods; Array<Method*>* _default_methods; ...... Array<u2>* _fields; }
这个类型定义了 Java 类的所有属性,包括注解、常量、内部类、方法、内部方法、字段等信息。这些信息本来被记录在 Class 文件中, ,InstanceKlass 是 它的内存形式。
可以和 Class 文件的结构图对比看。
可以看到,在 class 文件里的 constant pool,只能映射到 InstanceKlass 里的 constant 上。
resolve_from_stream 详解 判断是否允许并行加载类,并根据判断结果进行加锁 1 2 3 4 5 6 7 8 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文件。
解析文件流,生成 InstanceKlass 1 2 3 4 5 6 7 8 9 InstanceKlass* k = NULL ; k = KlassFactory::create_from_stream (st, class_name, loader_data, protection_domain, NULL , NULL , CHECK_NULL);
利用SystemDictionary注册生成的 Klass
SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDiction 并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个 ClassLoaderData 都保存着一个私有的 Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。
注册的代码:
1 2 3 4 5 6 7 8 9 10 11 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) { 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 中 中。
如果禁止了并行加载,那么直接利用SystemDictionary将 InstanceKlass 注册到 ClassLoader的 Dictionary 中即可。此时由锁保证数据唯一性。
ClassFileParser resolve_from_stream()最重要的是第二步,从文件流生成InstanceKlass,这依赖于调用 KlassFactory::create_from_stream()方法:
1 2 3 4 5 6 7 8 9 10 ClassFileParser parser (stream, name, loader_data, protection_domain, host_klass, cp_patches, ClassFileParser::BROADCAST, CHECK_NULL) ; InstanceKlass* result = parser.create_instance_klass (old_stream != stream, CHECK_NULL);
这又依赖于 ClassFileParser。
ClassFileParser 加载Class文件的入口便是 create_instance_klass()。顾名思义,用来创建InstanceKlass的。
create_instance_klass()主要就干了两件事:
为 InstanceKlass 分配内存:
1 2 InstanceKlass* const ik = InstanceKlass::allocate_instance_klass (*this , CHECK_NULL);
内存分配代码如下:
1 2 3 4 5 6 7 8 9 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 操作符进行了重载:
1 2 3 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():
1 2 3 4 5 6 7 8 9 MetaWord* Metaspace::allocate (ClassLoaderData* loader_data, size_t word_size, 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,所以当内存不够时会内存溢出。
分析Class文件,填充 InstanceKlass 内存区域:
1 fill_instance_klass (ik, changed_by_loadhook, CHECK_NULL);
ClassFileParser 在构造的时候就会开始分析Class文件,所以fill_instance_klass()中只需要填充即可。填充结束后,还会调用 java_lang_Class::create_mirror()创建 InstanceKlass 在Java 层的 Class 对象。双层对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); }
至此,系统保证了“不同ClassLoader加载的类是互相隔离的”。其基本流程为:每一个 classloader 有一个私有的 Dictionary,在加载类的过程中,Dictionary 有锁机制保证 InstanceKlass 的唯一性。如果存在不同的 ClassLoader 加载同一个 Class 文件,就会在内存里保存多份 InstanceKlass。而不同的 InstanceKlass 之间是不能相互强制转换的。
1 2 3 4 5 6 7 8 9 10 11 12 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("a.b.c" ); Student student = (Student) clazz.newInstance(); } }
上述代码运行一定会出错。
1 2 Exception in thread "main" java.lang .ClassCastException : a .b .c cannot be cast to a .b .c
那是因为目标 a.b.c 类型存在于当前类加载器中,而 clazz 这个Class实例则存在于 customClassloader 中,它的 clazz.newInstance() 的类型本身不能跨类加载器相互转化。
一种 hack 技巧 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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("a.b.c" ); Object object = clazz.newInstance(); Method method = clazz.getDeclaredMethod("test" ); method.invoke(object); } }
注意,这里面并不直接调用 c c = clazz.newInstance(),而是调用 Object object = clazz.newInstance(),并且必须使用反射才能调用 test。因为如果显式地使用了 C 这个类型,进行常量解析时,还是会跑到系统默认的 ClassLoader 加载的 C。