類的雙親委派機制
- 2020 年 7 月 19 日
- 筆記
前一篇介紹了3種類載入器,每種類載入器都載入指定路徑下的類庫,它們在具體使用時並不是相互獨立的,而是相互配合對類進行載入。另外如果有必要,還可以編寫自定義的類載入器。這些類載入器的的關係一般如下圖所示。
需要提示的是,上圖的雙親委派模型中的各個類載入器之間並不表示繼承關係,而是表示工作過程,具體說就是,對於一個載入類的具體請求,首先要委派給自己的父類去載入,只有當父類無法完成載入請求時,子類自己才會去嘗試載入。具體的委派邏輯實現在java.lang.ClassLoader類的loadClass()方法中。loadClass()方法的實現如下:
源程式碼位置:java/lang/ClassLoader.java protected Class<?> loadClass(Stringname,boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先從jvm快取查找該類 Class c = findLoadedClass(name); // (1) if (c ==null) { try { //然後委託給父類載入器進行載入 if (parent !=null) { c = parent.loadClass(name,false); (2) } else { //如果父類載入器為null,則委託給啟動類載入器載入 c = findBootstrapClassOrNull(name); (3) } } catch (ClassNotFoundException) { // 如果父類載入器拋出ClassNotFoundException異常, // 表明父類無法完成載入請求 } if (c ==null) { // 若仍然沒有找到則調用findClass()方法查找 c = findClass(name); (4) ... } } if (resolve) { resolveClass(c); //(5) } return c; } }
類的載入流程如下圖所示。
程式碼首先通過調用findLoadedClass()方法查找此類是否已經被載入過了,如果沒有,則需要優先調用父類載入器去載入。除了用C++實現的引導類載入器需要通過調用findBootstrapClassOrNull()方法外,其它用Java實現的類載入器都有parent欄位,因為這些類都繼承了ClassLoader這個基類(這個類中有對parent欄位的定義),如實現了擴展類載入器的ExtClassLoader類和實現了應用類載入器/系統類載入器的AppClassLoader類的繼承關係如下圖所示。
當父類無法實現載入請求時,也就是c為null時,當前類載入器調用findClass()方法嘗試自己完成載入請求。
編寫一個自定義的類載入器,如下:
實例1
package com.jvm; import java.net.URL; import java.net.URLClassLoader; public class UserClassLoader extends URLClassLoader { public UserClassloader(URL[] urls) { super(urls); } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { return super.loadClass(name, resolve); } }
可以看到UserClassLoader繼承了URLClassLoader類並覆寫了loadClass()方法,調用super.loadClass()方法其實就是在調用ClassLoader類中實現的loadClass()方法。
實例1(續)
package com.jvm; public class Student { }
實例1(續)
package com.jvm; import java.net.URL; public class TestClassLoader { public static void main(String[] args) throws Exception { URL url[] = new URL[1]; url[0] = Thread.currentThread().getContextClassLoader().getResource(""); UserClassLoader ucl = new UserClassLoader(url); Class clazz = ucl.loadClass("com.jvm.Student"); Object obj = clazz.newInstance(); } }
通過UserClassLoader類載入器載入Student類並通過調用Class.newInstance()方法獲取Student對象。
下面詳細介紹loadClass()方法中調用的findLoaderClass()、findBootstrapClassOrNull()與findClass()方法的實現。
1.findLoadedClass()方法
調用的findLoadedClass()方法的實現如下:
protected final Class<?> findLoadedClass(String name) { return findLoadedClass0(name); }
調用本地方法findLoadedClass0()方法,這個方法的實現如下:
源程式碼位置:hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name)) JVMWrapper("JVM_FindLoadedClass"); // THREAD表示當前執行緒 ResourceMark rm(THREAD); 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轉換成jclass return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror()); JVM_END
JVM_ENTRY是宏定義,用於處理JNI調用的預處理,如獲取當前執行緒的JavaThread指針。由於還會涉及到JNI實現的一些相關規則,如JNIHandles::resolve_non_null()、JNIHandles::resolve()與JNIHandles::mark_local(),主要就是JNI函數不能直接訪問Klass、oop對象只能藉助jobject、jclass等來訪問,為GC的處理提供便利,後面在介紹JNI時會詳細介紹。
調用的find_instance_or_array_klass()函數的實現如下:
// Look for a loaded instance or array klass by name. Do not do any loading. // return NULL in case of error. Klass* SystemDictionary::find_instance_or_array_klass(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) { Klass* k = NULL; assert(class_name != NULL, "class name must be non NULL"); if (FieldType::is_array(class_name)) { // The name refers to an array. Parse the name. // dimension and object_key in FieldArrayInfo are assigned as a // side-effect of this call FieldArrayInfo fd; BasicType t = FieldType::get_array_info(class_name, fd, CHECK_(NULL)); if (t != T_OBJECT) { k = Universe::typeArrayKlassObj(t); } else { k = SystemDictionary::find(fd.object_key(), class_loader, protection_domain, THREAD); } if (k != NULL) { k = k->array_klass_or_null(fd.dimension()); } } else { k = find(class_name, class_loader, protection_domain, THREAD); } return k; }
其中有對數組和類的查詢邏輯,方法並不涉及類的載入。對於數組的查找來說,首先要找到組成數組的基本元素的類型t,如果是基本類型,則調用Universe::typeArrayKlassObj()找到表示基本類型的Klass對象,在後面的類載入時會詳細介紹;對於元素類型是對象來說,調用SystemDictionary::find()方法。SystemDictionary類中的find()方法的實現如下:
源程式碼位置:hotspot/src/share/vm/classfile/systemDictionary.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()函數的實現如下:
源程式碼位置:hotspot/src/share/vm/classfile/dictionary.cpp Klass* Dictionary::find(int index, unsigned int hash, Symbol* name, ClassLoaderData* loader_data, Handle protection_domain, TRAPS) { //根據類名和類載入器計算對應的klass在map裡面對應的key DictionaryEntry* entry = get_entry(index, hash, name, loader_data); //存在,並且驗證通過則返回 if (entry != NULL && entry->is_valid_protection_domain(protection_domain)) { return entry->klass(); } else { //否者返回null,說明不存在 return NULL; } }
2.findBootstrapClassOrNull()方法
調用findBootstrapClassOrNull()方法請求引導類載入器完成載入請求,這個方法會調用本地方法findBootstrapClass()方法,源程式碼如下:
private Class<?> findBootstrapClassOrNull(String name){ return findBootstrapClass(name); } // return null if not found private native Class<?> findBootstrapClass(String name);
這個本地方法的實現如下:
源程式碼位置:/src/share/native/java/lang/ClassLoader.c JNIEXPORT jclass JNICALL Java_java_lang_ClassLoader_findBootstrapClass(JNIEnv *env, jobject loader, jstring classname) { jclass cls = 0; // ... cls = JVM_FindClassFromBootLoader(env, clname); // ... return cls; }
調用JVM_FindClassFromBootLoader()函數查找啟動類載入器載入的類,如果沒有查找到,方法會返回NULL。函數的實現如下:
源程式碼位置:hotspot/src/share/vm/prims/jvm.cpp JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name)) JVMWrapper2("JVM_FindClassFromBootLoader %s", name); // 檢查類名是否合法 if (name == NULL || (int)strlen(name) > Symbol::max_length()) { return NULL; } // 調用SystemDictionary解析目標類,如果未找到返回null TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL); Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL); if (k == NULL) { return NULL; } // 將Klass轉換成java中Class return (jclass) JNIHandles::make_local(env, k->java_mirror()); JVM_END
調用的SystemDictionary::resolve_or_null()函數的實現如下:
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)) { ResourceMark rm(THREAD); // Ignore wrapping L and ;. 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實例。方法的實現如下:
Klass* SystemDictionary::resolve_array_class_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) { assert(FieldType::is_array(class_name), "must be array"); Klass* k = NULL; FieldArrayInfo fd; // dimension and object_key in FieldArrayInfo are assigned as a side-effect // of this call BasicType t = FieldType::get_array_info(class_name, fd, CHECK_NULL); if (t == T_OBJECT) { // naked oop "k" is OK here -- we assign back into it 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 { k = Universe::typeArrayKlassObj(t); k = TypeArrayKlass::cast(k)->array_klass(fd.dimension(), CHECK_NULL); } return k; }
包含對元素類型為引用類型和元素類型為基本類型的Klass實例的查找。基本類型的查找和find_instance_or_array_klass()方法的實現基本類似。下面看調用的resolve_instance_class_or_null()方法對引用類型的查找,實現如下:
Klass* SystemDictionary::resolve_instance_class_or_null(Symbol* name, Handle class_loader, Handle protection_domain, TRAPS ){ // assert(name != NULL && !FieldType::is_array(name) && // !FieldType::is_obj(name), "invalid class name"); // UseNewReflection // Fix for 4474172; see evaluation for more details class_loader = Handle(THREAD, java_lang_ClassLoader::non_reflection_class_loader(class_loader())); ClassLoaderData *loader_data = register_loader(class_loader, CHECK_NULL); // Do lookup to see if class already exist and the protection domain has the right access // This call uses find which checks protection domain already matches // All subsequent calls use find_class, and set has_loaded_class so that // before we return a result we call out to java to check for valid protection domain // to allow returning the Klass* and add it to the pd_set if it is valid // 在變數SystemDictionary::_dictionary中查找是否類已經載入,如果載入就直接返回 Dictionary* dic = dictionary(); // 通過類名和類載入器計算hash值,class_loader是Handle類型,而Handle._value的類型是oop*。而loader_data是ClassLoaderData*類型 unsigned int d_hash = dic->compute_hash(name, loader_data); // 計算在hash中的索引位置 int d_index = dic->hash_to_index(d_hash); // 根據hash和index 查到對應的klassOop Klass* probe = dic->find(d_index, d_hash, name, loader_data,protection_domain, THREAD); if (probe != NULL){ return probe; // 如果直接找到的話,則返回 } // ... 省略其它查找的邏輯 }
如果類還沒有載入,那麼當前的方法還需要負責載入類。在實現的過程中考慮的因素比較多,比如解決並行載入、觸發父類的載入、域許可權的驗證等,不過這些都不是我們要討論的重點,我們僅看載入的過程,resolve_instance_class_or_null()方法中有如下調用:
// Do actual loading k = load_instance_class(name, class_loader, THREAD);
調用的方法如下:
// 體現出「雙親委派」機制,只要涉及到類的載入,都會調用這個函數 instanceKlassHandle SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) { instanceKlassHandle nh = instanceKlassHandle(); // null Handle if (class_loader.is_null()) { // 使用引導類載入器來載入類 // Search the shared system dictionary for classes preloaded into the shared spaces. // 在共享系統字典中搜索預載入到共享空間中的類,默認不使用共享空間,所以查找的結果為NULL instanceKlassHandle k; { k = load_shared_class(class_name, class_loader, THREAD); } if (k.is_null()) { // Use VM class loader,也就是系統類載入器進行類載入 k = ClassLoader::load_classfile(class_name, CHECK_(nh)); } // find_or_define_instance_class may return a different InstanceKlass // 調用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; } else { // 使用指定的類載入器載入,最終會調用java.lang.ClassLoader()這個Java方法去執行類載入 // Use user specified class loader to load class. Call loadClass operation on class_loader. ResourceMark rm(THREAD); JavaThread* jt = (JavaThread*) THREAD; Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh)); // Translate to external class name format, i.e., convert '/' chars to '.' 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)); // assert(result.get_type() == T_OBJECT, "just checking"); oop obj = (oop) result.get_jobject(); // 獲取調用loadClass()方法返回的Class對象 // Primitive classes return null since forName() can not be // used to obtain any of the Class objects representing primitives or void if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) { // 獲取Class對象表示的Java類,也就是獲取表示Java類的instanceKlass對象 instanceKlassHandle k = instanceKlassHandle(THREAD, java_lang_Class::as_Klass(obj)); // For user defined Java class loaders, check that the name returned is // the same as that requested. This check is done for the bootstrap // loader when parsing the class file. if (class_name == k->name()) { return k; } } // Class is not found or has the wrong name, return NULL return nh; } }
當class_loader為NULL時,表示使用啟動類載入器載入類,調用ClassLoader::load_classfile()方法載入類;當class_loader不為NULL時,會調用java.lang.ClassLoader類中的loadClass()方法,這在前面詳細介紹過,這裡不再介紹。
ClassLoader::load_classfile()方法的實現如下:
instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) { ResourceMark rm(THREAD); // Lookup stream for parsing .class file ClassFileStream* stream = NULL; int classpath_index = 0; { ClassPathEntry* e = _first_entry; while (e != NULL) { stream = e->open_stream(name, CHECK_NULL); if (stream != NULL) { break; } e = e->next(); ++classpath_index; } } instanceKlassHandle h; if (stream != NULL) { // class file found, parse it ClassFileParser parser(stream); ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data(); Handle protection_domain; TempNewSymbol parsed_name = NULL;
// 解析Class文件 instanceKlassHandle result = parser.parseClassFile(h_name,loader_data,protection_domain,parsed_name,false,CHECK_(h)); // add to package table if (add_package(name, classpath_index, THREAD)) { h = result; } } return h; }
方法首先從啟動類載入器應該查找的路徑下查找名稱為name的Class文件,如果找到,調用parser.parseClassFile()方法解析Class文件,這個方法非常重要,後面在介紹解析Class文件的過程時會詳細介紹。方法最後返回表示Java類的instanceKlass對象。
調用SystemDictionary::find_or_define_instance_class()方法可以支援並行載入,這個方法會調用SystemDictionary::update_dictionary()函數將已經載入的類添加到系統詞典Map裡面,如下:
源程式碼位置: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) { // Compile_lock prevents systemDictionary updates during compilations assert_locked_or_safepoint(Compile_lock); 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) { //添加kclass到系統詞典 dictionary()->add_klass(name, loader_data, k); notice_modification(); } ... }
其中key使用類的包路徑+類名,類載入器兩者確定,value則為具體載入的類對應的instanceKlassHandle對象,其中維護這kclass對象。也就是系統詞典裡面使用類載入器和類的包路徑類名唯一確定一個類。這也驗證了在Java中同一個類使用兩個類載入器進行載入後,載入的兩個類是不一樣的,是不能相互賦值的。
3.findClass()方法
調用findClass()方法完成類的載入請求,這個方法會調用本地函數defineClass1(),實現如下:
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(),實現如下:
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, jboolean verify, TRAPS) { JavaThread* jt = (JavaThread*) THREAD; // Since exceptions can be thrown, class initialization can take place // if name is NULL no check for class name in .class stream has to be made. TempNewSymbol class_name = NULL; if (name != NULL) { const int str_len = (int)strlen(name); class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL); } ResourceMark rm(THREAD); 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, // 載入Java主類 protection_domain, &st, verify != 0, CHECK_NULL); return (jclass) JNIHandles::make_local(env, k->java_mirror()); }
上面這段邏輯主要就是利用 ClassFileStream 將要載入的Class文件轉成文件流,然後調用SystemDictionary::resolve_from_stream()函數生成 Class 在 HotSpot 中的表示Klass。resolve_from_stream()函數的實現如下:
// Add a klass to the system from a stream (called by jni_DefineClass and // JVM_DefineClass). // Note: class_name can be NULL. In that case we do not know the name of // the class until we have parsed the stream. 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) { // Add class just loaded // If a class loader supports parallel classloading handle parallel define requests // find_or_define_instance_class may return a different InstanceKlass // 利用SystemDictionary註冊生成的 Klass // SystemDictionary 是用來幫助保存 ClassLoader 載入過的類資訊的。準確點說,SystemDictionary // 並不是一個容器,真正用來保存類資訊的容器是 Dictionary,每個ClassLoaderData 中都保存著一個私有的 // Dictionary,而 SystemDictionary 只是一個擁有很多靜態方法的工具類而已。 if (is_parallelCapable(class_loader)) { k = find_or_define_instance_class(class_name, class_loader, k, THREAD); } else { // 如果禁止了並行載入,那麼直接利用SystemDictionary將 InstanceKlass // 註冊到 ClassLoader的 Dictionary 中即可 define_instance_class(k, THREAD); } } return k(); }
調用parseClassFile()完成類的解析,然後調用find_or_define_instance_class()或define_instance_class()方法完成在SystemDictionary中的註冊。
實例2
更改實例1中的UserClassLoader類的loadClass()方法的實現,如下:
@Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("com.jvm")) { return findClass(name); } return super.loadClass(name, resolve); }
這樣像Student這樣的在com.jvm包下的類就會由用戶自定義的類載入器UserClassLoader類來載入了。
更改實例1中TestClassLoader類的實現,使用Student類型來接收clazz.newInstance()獲取到的Student對象,如下:
Student obj = (Student)clazz.newInstance();
實例運行後,拋出的異常的簡要資訊如下:
Exception in thread “main” java.lang.ClassCastException: com.jvm.Student cannot be cast to com.jvm.Student
因為實例化的Student對象所屬的InstanceKlass是由UserClassLoader載入生成的,而我們要強轉的類型Student對應的InstanceKlass是由系統默認的ClassLoader生成的,所以本質上它們就是兩個毫無關聯的InstanceKlass,當然不能強轉。
相關文章的鏈接如下:
1、在Ubuntu 16.04上編譯OpenJDK8的源程式碼
13、類載入器
作者持續維護的個人部落格classloading.com。
關注公眾號,有HotSpot源碼剖析系列文章!