Java反射學習總結(四)

類加載器

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.misc.Launcher$ExtClassLoader(Launcher 中的內部類);system loader 是由 Java寫的,實際對應sun.misc. Launcher$AppClassLoader(Launcher 中的內部類)。

流程如下圖:
這裏寫圖片描述

Bootstrap Loader 會查找系統參數 sun.boot.class.path 中指定位置的類,假設是 JRE classes 下之文件,或 lib 目錄下 .jar 文件中(例如 rt.jar)的類並加載,我們可以使用 System.getProperty(“sun.boot.class.path”) 來顯示 sun.boot.class.path 中指定的路勁,例如在我的終端顯示的是以下的路勁:

/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/classes

Extended Loader(sun.misc.Launcher$ExtClassLoader)是由 Java 寫的,會查找系統參數java.ext.dirs 中指定位置的類,假設是 JRE 目錄下的 lib\ext\classes 目錄下的 .class 文件,或 lib\ext 目錄下的 .jar 文件中(例如 rt.jar)的類並加載,我們可以使用 System.getProperty(“java.ext.dirs”) 來顯示指定的路勁:

/Users/lizhi/Library/Java/Extensions:/Library/Java/JavaVirtualMachines/jdk1.8.0_65.jdk/Contents/Home/jre/lib/ext:/Library/Java/Extensions:/Network/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java

System Loader(sun.misc.Launcher$AppClassLoader)是由 Java 寫的,會查找系統參 java.class.path 中指定位置的類,也就是 Classpath 所指定的路徑,假設是目前工作路徑下的 .class 文件,我們可以使用 System.getProperty(“java.class.path”) 來顯示 java.class.path 中指定的路徑,在使用 java 執行程序時,我們也可以加上 -cp 來覆蓋原有的 Classpath 設置,例如:

java –cp ./classes SomeClass

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。

package CoreJava.day_2;

/**
 * @author 李智
 * @date 2016/12/5
 */
public class SomeClass {
    public static void main(String[] args) {
        // 建立SomeClass實例
        SomeClass some = new SomeClass();
        // 取得SomeClass的Class實例
        Class c = some.getClass();
        // 取得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@60e53b93
sun.misc.Launcher$ExtClassLoader@66d3c617
null

Process finished with exit code 0

CoreJava.day_2.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下,並重新(任何目錄下)執行程序,我們可以看到:

null
Exception in thread "main" java.lang.NullPointerException
        at CoreJava.day_2.SomeClass.main(SomeClass.java:13)

由於 SomeClass 這次可以在 Bootstrap Loader 的設置路徑下找到,所以會由 Bootstrap Loader 來加載 SomeClass 類,Bootstrap Loader 通常由 C 寫的,在 Java 中沒有一個實際類來表示,所以顯示爲 null,因爲表示爲null,所以再由 null 上嘗試調用 getParent() 方法就會拋出 NullPointerException 異常。

取得 ClassLoader 的實例之後,我們可以使用它的 loadClass() 方法來加載類,使用 loadClass() 方法加載類時,不會執行靜態代碼塊,靜態代碼塊的執行會等到真正使用類時來建立實例:

package CoreJava.day_2;

/**
 * @author 李智
 * @date 2016/12/4
 */
public class TestClass2 {
        static {
            System.out.println("[執行靜態代碼塊]");
        }
}


package CoreJava.day_2;

/**
 * @author 李智
 * @date 2016/12/5
 */
public class ForNameDemoV3 {
    public static void main(String[] args) {
        try {
            System.out.println("加載TestClass2");
            ClassLoader loader = ForNameDemoV3.class.getClassLoader();
            Class c = loader.loadClass("CoreJava.day_2.TestClass2");

            System.out.println("TestClass2聲明");
            TestClass2 test = null;

            System.out.println("TestClass2實例對象");
            test = new TestClass2();
        } catch (ClassNotFoundException e) {
            System.out.println("找不到指定的類");
        }
    }
}

輸出:

加載TestClass2
TestClass2聲明
TestClass2實例對象
[執行靜態代碼塊]

Process finished with exit code 0

可以看出,loadClass() 不會在加載類時執行靜態代碼塊,而會在使用類new對象時才執行靜態代碼塊代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章