Java之類載入器(Class Loader)
JVM預設有三個類載入器:
-
Bootstrap Loader
Bootstrap Loader通常有C編寫,貼近底層作業系統。是JVM啟動後,第一個建立的類載入器。
-
Extended Loader
Extended Loader由Java編寫,由Bootstrap Loader建立。JVM啟動後,第二個被建立的類載入器。在Oracle JDK中,對應sum.misc.Launcher$ExtClassLoader($表示內部類)。
-
System Loader
System Loader由Java編寫,同樣由Bootstrap Loader建立
JVM啟動後,類載入器的建立順序如下:
- JVM建立Bootstrap Loader;
- 由Bootstrap Loader建立Extended Loader;
- 設定Bootstrap Loader為Extended Loader的父類;
- 用Bootstrap Loader建立System Loader;
- 設定Extended Loader為System Loader的父類。
如下圖:
從建立過程可見,類載入器間是有層級關係的
當類載入器有載入任務時,會先把載入任務交給父載入器,如果父載入器無法載入,才由自己載入。所以載入類的時候,會以Bootstrap Loader → Extended Loader → System Loader的載入類。如果所有載入器載入類失敗,丟擲java.lang.NoClassDefFoundError異常。
每個類載入器,會到其指定的目錄下,根據類名載入類檔案。三個預設類載入器的指定目錄保持在JVM的系統屬性裡。
Bootstrap Loader |
sun.boot.class.path 可以在編譯時期,使用-bootclasspath |
Extended Loader |
java.ext.dirs |
System Loader |
java.class.path 可以在執行程式時,使用-cp指令覆蓋CLASSPATH系統環境變數。 |
可以使用System.getProperty()方法獲取實際值。
三個預設類載入器在程式啟動後,就無法更改它們的搜尋目錄。如果在程式執行過程中,打算動態載入其他路徑下的類,可以建立java.net.URLClassLoader例項,使用新的類載入器。
URLClassLoader類建立例項時,需要java.net.URL陣列作為引數指定新的類載入搜尋路徑。
ClassLoader loader = new URLClassLoader(new URL[] {new URL(pathA), new URL(pathB)}); loader.loadClass(clzName); |
URLClassLoader類的例項,將由Bootstrap Loader建立,指定父載入器為System Loader。
由於使用URL協議,可以指定遠端伺服器上的類檔案,使用本地路徑時,注意新增字首"file:/"。
類載入器可以使用loadClass()方法載入類,預設不會執行類的靜態初始區塊。但會在第一次新建該類例項的時候執行靜態初始區塊。
可以使用getParent()獲取類載入器的父載入器。自定義物件預設用System Loader載入,可以使用Class.getClassLoader()獲取載入該類的類載入器。
// 獲取System Loader ClassLoader sysClassLoader = Empty.class.getClassLoader(); // 獲取Extended Loader ClassLoader extClassLoader = sysClassLoader.getParent(); // 獲取Bootstrap Loader ClassLoader bootClassLoader = extClassLoader.getParent();
System.out.println(sysClassLoader); System.out.println(extClassLoader); System.out.println(bootClassLoader); |
輸入如下:
null |
獲取Extended Loader的父載入器時,返回值為null,但並不代表它沒父載入器。因為Bootstrap Loader通常由C實現,在Java中沒實際類例項來表示,所有會顯示null。
標準API的類(包括陣列物件,包裝器),都是由Bootstrap Loader載入的。
// 以下均輸出null System.out.println(String.class.getClassLoader()); System.out.println(int[].class.getClassLoader()); System.out.println(Integer.class.getClassLoader()); System.out.println(Class.class.getClassLoader()); |
同一個類檔案,由同一個類載入器(實際載入的那個類載入器,注意載入任務會先向父載入器傳遞)多次載入,只有一個Class例項;如果由不同的類載入器載入,會由不同的Class例項。
參考資料:《Java學習筆記》 第17章