簡介類別載入器


Java在需要使用類別的時候,才會將類別載入,Java的類別載入是由類別載入器(Class loader)來達到的。

在文字模式下執行java XXX指令後,java執行程式會嘗試找到JRE安裝的所在目錄,然後尋找jvm.dll(預設是在JRE目錄下bin\client目錄中),接著啟動JVM並進行初始化動作,接著產生Bootstrap Loader,Bootstrap Loader會載入Extended Loader,並設定Extended Loader的parent為Bootstrap Loader,接著Bootstrap Loader會載入System Loader,並將System Loader的parent設定為Extended Loader。

Bootstrap Loader通常由C撰寫而成;Extended Loader是由Java所撰寫而成,如果是Sun JDK,實際是對應於sun.misc.Launcher\$ExtClassLoader(Launcher中的內部類別);System Loader是由Java撰寫而成,實際對應於sun.misc. Launcher\$AppClassLoader(Launcher中的內部類別)。


Bootstrap Loader會搜尋系統參數sun.boot.class.path中指定位置的類別,預設是JRE所在目錄的classes下之.class檔案,或lib目錄下.jar檔案中(例如rt.jar)的類別並載入。可以使用System.getProperty("sun.boot.class.path")陳述來顯示sun.boot.class.path中指定的路徑,例如在我的電腦中顯示的是以下的路徑:
C:\Program Files\Java\jdk1.6.0_16\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\lib\rt.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\lib\sunrsasign.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.6.0_16\jre\classes

Extended Loader(sun.misc.Launcher\$ExtClassLoader)是由Java撰寫而成,會搜尋系統參數java.ext.dirs中 指定位置的類別,預設是JRE目錄下的lib\ext\classes目錄下的.class檔案,或lib\ext目錄下的.jar檔案中的類別並載入。可以使用System.getProperty("java.ext.dirs")陳述來顯示java.ext.dirs中指定的路徑,例如在我的電腦中顯示的是以下的路徑:
C:\Program Files\Java\jdk1.6.0_16\jre\lib\ext;
C:\Windows\Sun\Java\lib\ext


System Loader(sun.misc.Launcher\$AppClassLoader)是由Java撰寫而成,會搜尋系統參數java.class.path中指定位置的類別,也就是Classpath所指定的路徑,預設是目前工作路徑下的.class檔案。可以使用System.getProperty("java.class.path")陳述來顯示java.class.path中指定的路徑,在使用java執行程式時,也可以加上-cp來覆蓋原有的Classpath設定。

Bootstrap Loader會在JVM啟動之後產生,之後它會載入Extended Loader並將其parent設為Bootstrap Loader,然後Bootstrap Loader再載入System Loader並將其parent設定為 ExtClassLoader,接著System Loader開始載入您指定的類別,在載入類別時,每個類別載入器會先將載入類別的任務交由其parent,如果parent找不到,才由自己負責載入, 所以在載入類別時,會以Bootstrap Loader→Extended Loader→System Loader的順序來尋找類別,如果都找不到,就會丟出 NoClassDefFoundError

類別載入器在Java中是以java.lang.ClassLoader型態存在, 每一個類別被載入後,都會有一個Class的實例來代表,而每個Class的實例都會記得自己是由哪個ClassLoader載入的,可以由Class的 getClassLoader()取得載入該類別的ClassLoader,而從ClassLoader的getParent()方法可以取得自己的 parent。

假設你有個自訂的SomeClass,則類別載入器之間的關係是:



如果你寫個程式來顯示這個關係:
    // 取得SomeClass的Class實例
    Class c = Class.forName("SomeClass");
    // 取得ClassLoader
    ClassLoader loader = c.getClassLoader();
    System.out.println(loader);
    // 取得父ClassLoader
    System.out.println(loader.getParent());
    // 再取得父ClassLoader
    System.out.println(loader.getParent().getParent());


則顯示的結果順序是:
sun.misc.Launcher\$AppClassLoader@19821f
sun.misc.Launcher\$ExtClassLoader@addbf1
null

SomeClass 是個自訂類別,你在目前的工作目錄下執行程式,首先AppClassLoader會將載入類別的任務交給ExtClassLoader,而 ExtClassLoader會將載入類別的任務交給Bootstrap Loader,由於Bootstrap Loader在它的路徑設定(sun.boot.class.path)下找不到類別,所以由ExtClassLoader來試著尋找,而 ExtClassLoader在它的路徑設定(java.ext.dirs)下也找不到類別,所以由AppClassLoader來試著尋找, AppClassLoader最後在Classpath(java.class.path)設定下找到指定的類別並載入。

載入 SomeClass的ClassLoader是AppClassLoader,而AppClassLoader的parent是 ExtClassLoader,而ExtClassLoader的parent是null,null並不是表示ExtClassLoader沒有設定 parent,而是因為Bootstrap Loader通常由C所撰寫而成,在Java中並沒有一個實際的類別來表示它,所以才會顯示為null。

如果把SomeClass的.class檔案移至JRE目錄下的lib\ext\classes下,並重新(於任何目錄下)執行程式,你會看到以下的訊息:
sun.misc.Launcher\$ExtClassLoader@addbf1
null
Exception in thread "main" java.lang.NullPointerException
        at Main.main(Main.java:12)

由 於SomeClass這次可以在ExtClassLoader的設定路徑下找到,所以會由ExtClassLoader來載入SomeClass類別,而 ExtClassLoader的parent顯示為null,指的是它的parent是由C撰寫而成的Bootstrap Loader,因為沒有實際的Java類別而表示為null,所以再由null上嘗試呼叫getParent()方法就會丟出 NullPointerException例外。

如果再把SomeClass的.class檔案移至JRE目錄下的classes目錄下,並重新(於任何目錄下)執行程式,您會看到以下的訊息:
null
Exception in thread "main" java.lang.NullPointerException
        at Main.main(Main.java:10)

由 於SomeClass這次可以在Bootstrap Loader的設定路徑下找到,所以會由Bootstrap Loader來載入SomeClass類別,Bootstrap Loader通常由C撰寫而成,在Java中沒有一個實際的類別來表示,所以顯示為null,因為表示為null,所以再由null上嘗試呼叫 getParent()方法就會丟出NullPointerException例外。

取得ClassLoader的實例之後,可以使用它的loadClass()方法來載入類別,使用loadClass()方法載入別時,不會執行靜態區塊,靜態區塊的執行會等到真正使用類別來建立實例時。