類加載器
- 2020 年 7 月 16 日
- 筆記
類加載器可以加載類,這些類被HotSpot加載後,都以Klass對象表示。涉及到的主要的類加載器有啟動類加載器/引導類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)和應用類加載器/系統類加載器(Application ClassLoader)。
1、引導類加載器/啟動類加載器
引導類加載器由ClassLoader類實現,這個ClassLoader類是用C++語言來實現的,它負責將 <JAVA_HOME>/lib目錄、 -Xbootclasspath選項指定的目錄或系統屬性sun.boot.class.path指定的目錄下的核心類庫加載到內存中。
用C++語言定義的類加載器及重要的函數如下:
class ClassLoader::AllStatic { private: ... // 加載類 static instanceKlassHandle load_classfile(Symbol* h_name,TRAPS); // 設置加載路徑 static void setup_bootstrap_search_path(); public: // 初始化類加載器 static void initialize(); ... }
load_classfile()方法可以根據類名加載類,具體實現如下:
源代碼位置:openjdk/hotspot/src/share/vm/classfile/classLoader.cpp instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) { // 獲取類名 const char* class_name = h_name->as_C_string(); .... stringStream st; st.print_raw(h_name->as_utf8()); st.print_raw(".class"); // 獲取文件名 const char* file_name = st.as_string(); ClassLoaderExt::Context context(class_name, file_name, THREAD); // ClassFileStream表示Class文件的位元組流 ClassFileStream* stream = NULL; int classpath_index = 0; ClassPathEntry* e = NULL; instanceKlassHandle h; { //從第一個ClassPathEntry開始遍歷所有的ClassPathEntry e = _first_entry; while (e != NULL) { stream = e->open_stream(file_name, CHECK_NULL); // 如果檢查返回false則返回null,check方法默認返回true if (!context.check(stream, classpath_index)) { return h; // NULL } // 如果找到目標文件則跳出循環 if (stream != NULL) { break; } e = e->next(); ++classpath_index; } } //如果找到了目標class文件 if (stream != NULL) { // 構建一個ClassFileParser實例 ClassFileParser parser(stream); // 構建一個ClassLoaderData實例 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, context.should_verify(classpath_index), THREAD); ... // 調用ClassLoader的add_package方法,把當前類的包名加入到_package_hash_table中 h = context.record_result(classpath_index, e, result, THREAD); } ... return h; }
parseClassFile()方法就是解析Class文件中的類、字段、常量池等信息,然後轉換為C++內部的對等表示,如類元信息存儲在InstanceKlass實例中,常量池信息存儲在ConstantPool中,部分的C++對等實現(類模型)在之前已經介紹過,這裡不再介紹。後續會詳細介紹parseClassFile()方法解析Class文件的過程。
2、擴展類加載器
擴展類加載器由ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現,負責將 <JAVA_HOME >/lib/ext目錄或者由系統變量-Djava.ext.dir所指定的目錄中的類庫加載到內存中。
用Java語言編寫的擴展類加載器的實現如下:
源代碼位置: static class ExtClassLoader extends URLClassLoader { /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); // 獲取要加載類的加載路徑 ... return new ExtClassLoader(dirs); // 實例化擴展類加載器 ... } /* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); // parent傳遞的參數為null,所以並不是引導類加載器 } private static File[] getExtDirs() { String s = System.getProperty("java.ext.dirs"); File[] dirs; if (s != null) { StringTokenizer st = new StringTokenizer(s, File.pathSeparator); int count = st.countTokens(); dirs = new File[count]; for (int i = 0; i < count; i++) { dirs[i] = new File(st.nextToken()); } } else { dirs = new File[0]; } return dirs; } ... }
ExtClassLoader類的構造函數中在調用父類的構造函數時,傳遞的第2個參數的值為null,這個值最終會賦值給parent字段,所以後面將會講到,當這個字段的值為null時,ClassLoader類中實現的loadClass()方法會調用findBootstrapClassOrNull()方法加載類,最終會調用C++實現的ClassLoader類的相關方法。
3、系統類加載器/應用類加載器
系統類加載器由AppClassLoader(sun.misc.Launcher$AppClassLoader)實現,負責將由系統環境變量-classpath、-cp或系統屬性java.class.path指定的路徑下的類庫加載到內存中。
用Java語言編寫的擴展類加載器的實現如下:
源代碼位置: static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl)throws IOException { final String s = System.getProperty("java.class.path"); final File[] path = (s == null) ? new File[0] : getClassPath(s); ... return new AppClassLoader(urls, extcl); } /* * Creates a new AppClassLoader */ AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); // parent參數是一個擴展類加載器實例 } /** * Override loadClass so we can checkPackageAccess. */ public Class loadClass(String name, boolean resolve)throws ClassNotFoundException{ ... return (super.loadClass(name, resolve)); } ... }
在Launcher類的構造函數中實例化系統類加載器時,會調用getAppClassLoader()方法獲取系統類加載器,傳入的參數是一個擴展類加載器實例,這樣系統類加載器的父加載器就變成了擴展類加載器。用戶自定義的無參加載器的父類加載器默認就是AppClassloader加載器。
4、構造類加載器實例
HotSpot在啟動過程中會在rt.jar包裏面的sun.misc.Launcher類中完成擴展類加載器和系統類加載器的實例化,也會進行引導類加載器的初始化,也就是調用C++語言編寫的ClassLoader類的initialize()方法。
HotSpot在初始化時,會初始化一個重要的變量,定義如下:
源代碼位置:hotspot/src/share/vm/classfile/systemDictionary.cpp
oop SystemDictionary::_java_system_loader = NULL;
這個屬性保存系統類加載器實例,HotSpot在加載主類時會使用這個類加載器加載主類。屬性在compute_java_system_loader()方法中初始化,調用鏈路如下:
JavaMain() java.c InitializeJVM() java.c JNI_CreateJavaVM() jni.cpp Threads::create_vm() thread.cpp SystemDictionary::compute_java_system_loader() systemDictionary.cpp
方法的實現如下:
void SystemDictionary::compute_java_system_loader(TRAPS) { KlassHandle system_klass(THREAD, WK_KLASS(ClassLoader_klass)); JavaValue result(T_OBJECT);
// 調用java.lang.ClassLoader類的getSystemClassLoader()方法 JavaCalls::call_static(&result, // 調用Java靜態方法的返回值存儲在result中 KlassHandle(THREAD, WK_KLASS(ClassLoader_klass)), // 調用的目標類為java.lang.ClassLoader vmSymbols::getSystemClassLoader_name(), // 調用目標類中的目標方法為getSystemClassLoader vmSymbols::void_classloader_signature(), // 調用目標方法的方法簽名 CHECK); // 獲取調用getSystemClassLoader()方法的結果並保存到_java_system_loader屬性中 _java_system_loader = (oop)result.get_jobject(); // 初始化屬性為系統類加載器/應用類加載器/AppClassLoader }
通過JavaClass::call_static()方法調用java.lang.ClassLoader類的getSystemClassLoader()方法。JavaClass::call_static()方法非常重要,它是HotSpot調用Java靜態方法的API,後面傳經詳細介紹。
下面看一下getSystemClassLoader()方法的實現,如下:
public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); // 獲取Launcher實例 if (l != null) { scl = l.getClassLoader(); // ... } sclSet = true; } }
調用Launcerh.getLauncher()方法獲取Launcher實例,實例通過靜態變量launcher來保存,靜態變量的定義如下:
private static Launcher launcher = new Launcher();
調用l.getClassLoader()方法獲取類加載器實例,如下:
public ClassLoader getClassLoader() { return loader; // 返回的loader就是Launcher類的loader,也就是系統類加載器AppClassLoader }
Launcher()類的構造函數如下:
public Launcher() { // Create the extension class loader ClassLoader extcl; try { // 首先創建了擴展類加載器 extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError("Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { // 以ExtClassloader作為父加載器創建了AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError("Could not create application class loader", e); } // Also set the context class loader for the primordial thread. // 默認線程上下文加載器為AppClassloader Thread.currentThread().setContextClassLoader(loader); }
可以看到有對ExtClassLoader與AppClassLoader實例創建的邏輯,這樣HotSpot就可以通過_java_system_loader屬性獲取AppClassLoader實例,通過AppClassLoader實例中的parent屬性使用ExtClassLoader。
相關文章的鏈接如下:
1、在Ubuntu 16.04上編譯OpenJDK8的源代碼
關注公眾號,有HotSpot源碼剖析系列文章!