第3.2篇-类的双亲委派机制
前面介绍了3种类加载器,每种类加载器都加载指定路径下的类库,它们在具体使用时并不是相互独立的,而是相互配合对类进行加载。另外如果有必要,还可以编写自定义的类加载器。这些类加载器的关系一般如下图所示。
需要提示的是,上图的各个类加载器之间并不表示继承关系,而是表示工作过程,具体说就是,对于一个加载类的具体请求,首先要委派给自己的父类加载器去加载,只有当父类加载器无法完成加载请求时,子类加载器自己才会去尝试加载,这就叫“双亲委派”。具体的委派逻辑在java.lang.ClassLoader类的loadClass()方法中实现。loadClass()方法的实现如下:
源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java protected Class<?> loadClass(Stringname,boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先从HotSpot VM缓存查找该类 Class c = findLoadedClass(name); if (c ==null) { try { // 然后委派给父类加载器进行加载 if (parent !=null) { c = parent.loadClass(name,false); } else { // 如果父类加载器为null,则委派给启动类加载器加载 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException) { // 如果父类加载器抛出ClassNotFoundException异常, // 表明父类无法完成加载请求 } if (c ==null) { // 当前类加载器尝试自己加载类 c = findClass(name); ... } } ... return c; } }
类的加载流程如下图所示。
首先调用findLoadedClass()方法查找此类是否已经被加载过了,如果没有,优先调用父类加载器去加载。除了用C++实现的引导类加载器需要通过调用findBootstrapClassOrNull()方法加载外,其它用Java实现的类加载器一般都有parent字段(定义在java.lang.ClassLoader类中的字段),所以可直接调用parent的loadClass()方法委派加载请求。除了引导类加载器外,其它加载器都继承了java.lang.ClassLoader这个基类,如实现了扩展类加载器的ExtClassLoader类和实现了应用类加载器的AppClassLoader类的继承关系如下图所示。
当父类无法完成加载请求时,也就是c为null时,当前类加载器调用findClass()方法尝试自己完成类加载的请求。
编写一个自定义的类加载器,如下:
实例1
package com.jvm; import java.net.URL; import java.net.URLClassLoader; public class UserClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { return super.loadClass(name, resolve); } }
可以看到UserClassLoader继承了ClassLoader类并覆写了loadClass()方法,调用super.loadClass()方法其实就是在调用ClassLoader类中实现的loadClass()方法。
实例1(续)
package com.jvm; public class Student { }
实例1(续)
package com.jvm; public class TestClassLoader { public static void main(String[] args) throws Exception { UserClassLoader ucl = new UserClassLoader(); Class clazz = ucl.loadClass("com.jvm.Student"); Object obj = clazz.newInstance(); } }
通过UserClassLoader类加载器加载Student类并通过调用Class.newInstance()方法获取Student对象。
下面详细介绍loadClass()方法中调用的findLoaderClass()、findBootstrapClassOrNull()与findClass()方法的实现。
1.findLoadedClass()方法
findLoadedClass()方法调用本地函数findLoadedClass0()判断类是否已经加载。findLoadedClass()方法的实现如下:
protected final Class<?> findLoadedClass(String name) { return findLoadedClass0(name); }
findLoadedClass0()函数的实现如下:
源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader, jstring name) { if (name == NULL) { return 0; } else { return JVM_FindLoadedClass(env, loader, name); } }
调用的JVM_FindLoadedClass()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name)) Handle h_name (THREAD, JNIHandles::resolve_non_null(name)); // 获取类名对应的Handle Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL); // 检查类名是否为空 const char* str = java_lang_String::as_utf8_string(string()); if (str == NULL) return NULL; // 判断类名是否过长 const int str_len = (int)strlen(str); if (str_len > Symbol::max_length()) { return NULL; } // 创建一个临时的Symbol实例 TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL); // 获取类加载器对应的Handle Handle h_loader(THREAD, JNIHandles::resolve(loader)); // 查找目标类是否存在 Klass* k = SystemDictionary::find_instance_or_array_klass(klass_name,h_loader,Handle(),CHECK_NULL); // 将Klass实例转换成java.lang.Class对象 return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror()); JVM_END
JVM_ENTRY是宏定义,用于处理JNI调用的预处理,如获取当前线程的JavaThread指针。因为垃圾回收等原因,JNI函数不能直接访问Klass、oop实例,只能借助jobject、jclass等来访问,所以会调用JNIHandles::resolve_non_null()、JNIHandles::resolve()与JNIHandles::mark_local()等函数进行转换。
调用的SystemDictionary::find_instance_or_array_klass()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp // 查找InstanceKlass或ArrayKlass实例,不进行任何类加载操作 Klass* SystemDictionary::find_instance_or_array_klass( Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS ){ Klass* k = NULL; if (FieldType::is_array(class_name)) { // 数组的查找逻辑 FieldArrayInfo fd; // 获取数组的元素类型 BasicType t = FieldType::get_array_info(class_name, fd, CHECK_(NULL)); if (t != T_OBJECT) { // 元素类型为Java基本类型 k = Universe::typeArrayKlassObj(t); } else { // 元素类型为Java对象 Symbol* sb = fd.object_key(); k = SystemDictionary::find(sb, class_loader, protection_domain, THREAD); } if (k != NULL) { // class_name可能表示的是多维数组,所以可能需要根据维度创建出ObjArrayKlass实例 k = k->array_klass_or_null(fd.dimension()); } } else { // 类的查找逻辑 k = find(class_name, class_loader, protection_domain, THREAD); } return k; }
如上函数包含了对数组和类的查询逻辑,方法并不涉及类的加载。如果是数组,首先要找到数组的元素类型t,如果是基本类型,调用Universe::typeArrayKlassObj()函数找到TypeArrayKlass实例,如果基本元素的类型是对象,调用SystemDictionary::find()方法从字典中查找InstanceKlass实例,所有已加载的InstanceKlass实例都会存储到字典中。
查找对象的SystemDictionary::find()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp Klass* SystemDictionary::find( Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS ) { ... class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader())); ClassLoaderData* loader_data = ClassLoaderData::class_loader_data_or_null(class_loader()); ... unsigned int d_hash = dictionary()->compute_hash(class_name, loader_data); int d_index = dictionary()->hash_to_index(d_hash); { ... return dictionary()->find(d_index, d_hash, class_name, loader_data,protection_domain, THREAD); } }
将已经加载的类存储在Dictionary中,为了加快查找采用了hash存储。只有类加载器和类才能确定唯一的表示Java类的Klass实例,所以在计算d_hash时必须传入class_name和loader_data这两个参数。计算出具体索引d_index后,就可以调用Dictionary类的find()函数进行查找了。调用的Dictionary::find()函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp Klass* Dictionary::find( int index, unsigned int hash, Symbol* name, ClassLoaderData* loader_data, Handle protection_domain, TRAPS ) { // 根据类名和类加载器计算对应的Klass实例在字典里的存储的key DictionaryEntry* entry = get_entry(index, hash, name, loader_data); if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) { return entry->klass(); } else { return NULL; } }
调用get_entry()函数从hash表中查找Klass实例,如果找到并且验证是合法的,则返回Klass实例,否则返回NULL。
findLoadedClass()方法执行的流程如下图所示。
2.findBootstrapClassOrNull()方法
调用findBootstrapClassOrNull()方法请求引导类加载器完成类的加载请求,这个方法会调用本地函数findBootstrapClass(),此函数的实现如下:
源代码位置:openjdk/jdk/src/share/classes/java/lang/ClassLoader.java private Class<?> findBootstrapClassOrNull(String name){ return findBootstrapClass(name); } private native Class<?> findBootstrapClass(String name);
这个本地函数在HotSpot VM中的实现如下:
源代码位置:openjdk/jdk/src/share/native/java/lang/ClassLoader.c JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_findBootstrapClass( JNIEnv *env, jobject loader, jstring classname ){ char *clname; jclass cls = 0; char buf[128]; if (classname == NULL) { return 0; } clname = getUTF(env, classname, buf, sizeof(buf)); ... cls = JVM_FindClassFromBootLoader(env, clname); ... return cls; }
调用JVM_FindClassFromBootLoader()函数查找启动类加载器加载的类,如果没有查到,函数会返回NULL。函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name)) // 检查类名是否合法 if (name == NULL || (int)strlen(name) > Symbol::max_length()) { return NULL; } TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL); // 调用SystemDictionary.resolve_or_null()函数解析目标类,如果未找到,返回NULL Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL); if (k == NULL) { return NULL; } // 将Klass实例转换成java.lang.Class对象 return (jclass) JNIHandles::make_local(env, k->java_mirror()); JVM_END
调用SystemDictionary::resolve_or_null()函数对类进行查找,函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp Klass* SystemDictionary::resolve_or_null(Symbol* class_name, TRAPS) { return resolve_or_null(class_name, Handle(), Handle(), THREAD); } Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) { // 数组,通过签名的格式来判断 if (FieldType::is_array(class_name)) { return resolve_array_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL); } // 普通类,通过签名的格式来判断 else if (FieldType::is_obj(class_name)) { // 去掉签名中的开头字符L和结束字符; TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1, class_name->utf8_length() - 2, CHECK_NULL); return resolve_instance_class_or_null(name, class_loader, protection_domain, CHECK_NULL); } else { return resolve_instance_class_or_null(class_name, class_loader, protection_domain, CHECK_NULL); } }
调用resolve_array_class_or_null()函数查找数组时,如果数组元素的类型为对象类型,同样会调用resolve_instance_class_or_null()函数查找类对应的Klass实例。函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDicitonary.cpp Klass* SystemDictionary::resolve_array_class_or_null( Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS ){ Klass* k = NULL; FieldArrayInfo fd; // 获取数组元素的类型 BasicType t = FieldType::get_array_info(class_name, fd, CHECK_NULL); if (t == T_OBJECT) { // 数组元素的类型为对象 Symbol* sb = fd.object_key(); k = SystemDictionary::resolve_instance_class_or_null(sb,class_loader,protection_domain,CHECK_NULL); if (k != NULL) { k = k->array_klass(fd.dimension(), CHECK_NULL); } } else { // 数组元素的类型为Java基本类型 k = Universe::typeArrayKlassObj(t); int x = fd.dimension(); TypeArrayKlass* tak = TypeArrayKlass::cast(k); k = tak->array_klass(x, CHECK_NULL); } return k; }
对元素类型为对象类型和元素类型为基本类型的一维数组的Klass实例进行查找。查找基本类型的一维数组和find_instance_or_array_klass()函数的实现类似。下面看调用的resolve_instance_class_or_null()函数对对象类型的查找,实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp Klass* SystemDictionary::resolve_instance_class_or_null( Symbol* name, Handle class_loader, Handle protection_domain, TRAPS ){ // 在变量SystemDictionary::_dictionary中查找是否类已经加载,如果加载就直接返回 Dictionary* dic = dictionary(); // 通过类名和类加载器计算hash值 unsigned int d_hash = dic->compute_hash(name, loader_data); // 计算在hash表中的索引位置 int d_index = dic->hash_to_index(d_hash); // 根据hash和index 查到对应的Klass实例 Klass* probe = dic->find(d_index, d_hash, name, loader_data,protection_domain, THREAD); if (probe != NULL){ return probe; // 如果从字典中找到就直接返回 } ... // 从字典中没有找到时,需要对类进行加载 if (!class_has_been_loaded) { k = load_instance_class(name, class_loader, THREAD); ... } ... }
如果类还没有加载,那么当前的函数还需要负责加载类。在实现的过程中考虑的因素比较多,比如解决并行加载、触发父类的加载、域权限的验证等,不过这些都不是要讨论的重点。当类没有加载时,调用load_instance_class()函数进行加载。
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp // 体现出“双亲委派”机制,只要涉及到类的加载,都会调用这个函数 instanceKlassHandle SystemDictionary::load_instance_class( Symbol* class_name, Handle class_loader, TRAPS ) { instanceKlassHandle nh = instanceKlassHandle(); // 空的Handle if (class_loader.is_null()) { // 使用引导类加载器来加载类 // 在共享系统字典中搜索预加载到共享空间中的类,默认不使用共享空间,所以查找的结果为NULL instanceKlassHandle k; { k = load_shared_class(class_name, class_loader, THREAD); } if (k.is_null()) { // 使用引导类加载器进行类加载 k = ClassLoader::load_classfile(class_name, CHECK_(nh)); } // 调用SystemDictionary::find_or_define_instance_class->SystemDictionary::update_dictionary // -> Dictionary::add_klass()将生成的Klass实例存起来。 // Dictionary是个hash表实现,使用的也是开链法解决hash冲突 if (!k.is_null()) { // 支持并行加载,也就是允许同一个类加载器同时加载多个类 k = find_or_define_instance_class(class_name, class_loader, k, CHECK_(nh)); } return k; } // 使用指定的类加载器加载,最终会调用java.lang.ClassLoader类中的loadClass()方法执行类加载 else { JavaThread* jt = (JavaThread*) THREAD; Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh)); Handle string = java_lang_String::externalize_classname(s, CHECK_(nh)); JavaValue result(T_OBJECT); KlassHandle spec_klass (THREAD, SystemDictionary::ClassLoader_klass()); // 调用java.lang.ClassLoader对象中的loadClass()方法进行类加载 JavaCalls::call_virtual(&result, class_loader, spec_klass, vmSymbols::loadClass_name(), vmSymbols::string_class_signature(), string, CHECK_(nh)); // 获取调用loadClass()方法返回的java.lang.Class对象 oop obj = (oop) result.get_jobject(); // 调用loadClass()方法加载的必须是对象类型 if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) { // 获取java.lang.Class对象表示的Java类,也就是获取表示Java类的instanceKlass实例 instanceKlassHandle k = instanceKlassHandle(THREAD, java_lang_Class::as_Klass(obj)); if (class_name == k->name()) { return k; } } // Class文件不存在或名称错误,返回空的Handle实例 return nh; } }
当class_loader为NULL时,表示使用启动类加载器加载类,调用ClassLoader::load_classfile()函数加载类;当class_loader不为NULL时,会调用java.lang.ClassLoader类中的loadClass()方法加载。这种判断逻辑也是“双亲委派”逻辑的体现。
findBootstrapClassOrNull()方法执行的流程如下图所示。
使用引导类加载器加载类时,调用ClassLoader::load_classfile()函数加载类,如果得到了Klass实例,随后调用的SystemDictionary::find_or_define_instance_class()函数会将这个Klass实例添加到字典中。函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp instanceKlassHandle SystemDictionary::find_or_define_instance_class( Symbol* class_name, Handle class_loader, instanceKlassHandle k, TRAPS ) { instanceKlassHandle nh = instanceKlassHandle(); // 空的Handle Symbol* name_h = k->name(); ClassLoaderData* loader_data = class_loader_data(class_loader); unsigned int d_hash = dictionary()->compute_hash(name_h, loader_data); int d_index = dictionary()->hash_to_index(d_hash); ... { MutexLocker mu(SystemDictionary_lock, THREAD); // 检查一下类是否已经加载过了,如果已经加载过,则存在对应的InstanceKlass实例 if (UnsyncloadClass || (is_parallelDefine(class_loader))) { Klass* check = find_class(d_index, d_hash, name_h, loader_data); if (check != NULL) { return(instanceKlassHandle(THREAD, check)); } } ... } // 在SystemDictionary::load_instance_class()函数里已经调用ClassLoader::load_classfile() // 函数加载了类,所以这里只需要创建InstanceKlass实例并保存到字典中即可 define_instance_class(k, THREAD); ... return k; }
函数同样会调用find_class()函数从字典中检查一下这个类是否已经保存到系统字典中了(因为并行加载的原因,其它线程可能已经创建了InstanceKlass实例并保存到字典中了),如果找到实例就直接返回,否则调用define_instance_class()函数定义一个InstanceKlass实例并保存到字典中,此函数会最终调用SystemDictionary::update_dictionary()函数将已经加载的类添加到系统词典SystemDictionary里面,如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp void SystemDictionary::update_dictionary( int d_index, unsigned int d_hash, int p_index, unsigned int p_hash, instanceKlassHandle k, Handle class_loader, TRAPS ) { Symbol* name = k->name(); ClassLoaderData *loader_data = class_loader_data(class_loader); { MutexLocker mu1(SystemDictionary_lock, THREAD); Klass* sd_check = find_class(d_index, d_hash, name, loader_data); if (sd_check == NULL) { dictionary()->add_klass(name, loader_data, k); } } }
SystemDictionary用来保存类加载器加载过的类信息。准确点说,SystemDictionary并不是一个容器,真正用来保存类信息的容器是Dictionary,每个ClassLoaderData中都保存着一个私有的Dictionary,而SystemDictionary只是一个拥有很多静态函数的工具类而已。
Dictionary的底层数据存储结构为hash,key使用类的包路径+类名和类加载器两者确定,value则为具体加载的类对应的instanceKlassHandle实例。所以在系统词典里面使用类加载器和类的包路径+类名唯一确定一个类。这也验证了在Java中同一个类使用两个类加载器进行加载后,加载的两个类是不一样的,是不能相互赋值的。
3.findClass()方法
调用findClass()方法完成类的加载请求,这个方法会调用本地函数defineClass1(),definClass1()对应的本地方法为Java_java_lang_ClassLoader_defineClass1(),实现如下:
源代码位置:openjdk/jdk/src/share/native/java/lang/ClassLoader.c 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_define_class_common()函数。核心的实现逻辑如下:
源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY(jclass, JVM_DefineClassWithSource(JNIEnv *env,const char *name, jobject loader,const jbyte *buf,jsize len,jobject pd,const char *source )) return jvm_define_class_common(env, name, loader, buf, len, pd, source, true, THREAD); JVM_END static jclass jvm_define_class_common(JNIEnv *env,const char *name,jobject loader, const jbyte *buf,jsize len,jobject pd,const char *source,jboolean verify,TRAPS ) { TempNewSymbol class_name = NULL; // 在HotSpot VM中,字符串使用Symbol实例表示,以达到重用和唯一的目的 if (name != NULL) { const int str_len = (int)strlen(name); class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL); } ClassFileStream st((u1*) buf, len, (char *)source); Handle class_loader (THREAD, JNIHandles::resolve(loader)); Handle protection_domain (THREAD, JNIHandles::resolve(pd)); Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader, protection_domain, &st,verify != 0,CHECK_NULL); return (jclass) JNIHandles::make_local(env, k->java_mirror()); }
利用ClassFileStream将要加载的Class文件转成文件流,然后调用SystemDictionary::resolve_from_stream()函数生成Klass实例。函数的实现如下:
源代码位置:openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp Klass* SystemDictionary::resolve_from_stream(Symbol* class_name, Handle class_loader, Handle protection_domain, ClassFileStream* st, bool verify, TRAPS) { ... // 解析文件流,生成InstanceKlass ClassFileParser cfp = ClassFileParser(st); instanceKlassHandle k = cfp.parseClassFile(class_name, loader_data, protection_domain, parsed_name, verify, THREAD); ... if (!HAS_PENDING_EXCEPTION) { if (is_parallelCapable(class_loader)) { // 支持并行加载 k = find_or_define_instance_class(class_name, class_loader, k, THREAD); } else { // 如果禁止了并行加载,那么直接利用SystemDictionary将InstanceKlass实例 // 注册到SystemDictionary中 define_instance_class(k, THREAD); } } return k(); }
调用parseClassFile()函数完成类的解析之后会生成Klass实例,调用find_or_define_instance_class()或define_instance_class()函数将Klass实例注册到SystemDictionary中。关于parseClassFile()函数将在第4章详细介绍。
findClass()方法的执行流程如图3-5所示。
SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDictionary 并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个ClassLoaderData 中都保存着一个私有的 Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。
如上省略了一些逻辑,如支持并行加载等逻辑。如果允许并行加载,那么前面就不会对 ClassLoader 加锁,所以在同一时刻,可能对同一 Class 文件加载了多次。
但是同一 Class 在同一 ClassLoader 中必须保持唯一性,所以这里会先利用 SystemDictionary 查询 ClassLoader 是否已经加载过相同 Class。
如果已经加载过,那么就将当前线程刚刚加载的 InstanceKlass 加入待回收列表,并将 InstanceKlass* k 重新指向利用 SystemDictionary 查询到的 InstanceKlass。如果没有查询到,那么就将刚刚加载的 InstanceKlass 注册到 ClassLoader 的 Dictionary 中。 虽然并行加载不会锁住 ClassLoader ,但是会在注册 InstanceKlass 时对 SystemDictionary 加锁,所以不需要担心 InstanceKlass 在注册时的并发操作。如果禁止了并行加载,那么直接利用 SystemDictionary 将 InstanceKlass 注册到 ClassLoader 的 Dictionary 中即可。
参考:https://zhuanlan.zhihu.com/p/60328095
实例2
更改实例1中的UserClassLoader类的loadClass()方法的实现,如下:
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("com.classloading")) { return findClass(name); } return super.loadClass(name, resolve); }
这样像Student这样的在com.classloading包下的类就会由用户自定义的类加载器UserClassLoader类来加载了。
更改实例中TestClassLoader类的实现,使用Student类型来接收clazz.newInstance()获取到的Student对象,如下:
Student obj = (Student)clazz.newInstance();
实例运行后,抛出的异常的简要信息如下:
Exception in thread "main" java.lang.ClassCastException: com.classloading.Student cannot be cast to com.classloading.Student
因为实例化的Student对象所属的InstanceKlass实例是由UserClassLoader加载生成的,而我们要强制转换的Student类型对应的InstanceKlass实例是由系统默认的AppClassLoader生成的,所以本质上它们是两个毫无关联的InstanceKlass实例,当然不能强制转换。
参考文章:
(1)类加载器的实现
(2)类的预加载
(3)Java类的加载