java類加載機制

類生命週期

  • java中,類型的加載、連接、初始化都是在程序運行期完成,而不是在編譯期完成。
  • 類的生命週期,包括7個階段。
    180526.life.png
  • 爲了支持動態綁定(根據實例的運行期類型調用相應的方法),解析階段可以在初始化之後開始。

類加載時機

  • 類的加載時機沒有強制約束,交由虛擬機把握,初始化有且只有5種情況,稱爲對一個類進行主動引用,其他行爲不會觸發初始化,稱爲被動引用
  • 接口初始化,不要求父接口全部初始化。

主動引用

  • 遇到new,getstatic,putstatic,invokestatic這4條指令碼是,如類沒有初始化,則進行初始化。場景包括:
    • new創建實例。
    • 讀取或設置類的靜態字段(被final static修飾在編譯期放入常量池的靜態字段除外)。
    • 調用一個類的靜態方法。
  • 對類進行反射調用,如類沒有初始化,則進行初始化.
  • 初始化一個類,如父類沒有初始化,則父類進行初始化。
  • 虛擬機啓動時,用戶指定的要執行的主類(main方法),需要先初始化這個主類。
  • jdk1.7 動態語言支持 ,MethodHandle實例的解析結果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,並且這個方法句柄對應的類沒有初始化,需要對類進行初始化。MethodHandle

被動引用

  • 通過子類引用父類的靜態字段,只會導致父類的初始化,不會導致子類的初始化。(也可以調節參數,使得子類同時被加載)。
  • 通過數組定義引用類,不會初始化這個類。
SuperClass[] a = new SuperClass[10];
  • 常量編譯階段存入調用類的常量池,並沒有直接引用到定義常量的類,不會觸發類的初始化。

類加載過程

加載

  • 是類加載的一個階段。包括3件事。
    • 通過類的全限定名獲取類的二進制字節流
    • 將字節流代表的靜態存儲結構轉化爲方法區運行時數據結構
    • 內存中生成一個該類的Class對象,在方法區中,作爲這個類數據的訪問入口。
  • 字節流的來源包括:zip,jar等壓縮包;網絡;動態代理;jsp生成的Class類;數據庫。
  • 可以通過自定義的類加載器控制字節流的獲取方式,即重寫一個類加載器的loadClass方法。

驗證

  • Class文件並不一定是從java源碼編譯而來,需要校驗。
  • 包括文件格式、元數據、字節碼、符號引用的驗證。

準備

  • 設置變量的初始值,通常是該數據類型的零值
  • 如果變量爲final,則直接初始化爲指定值。

解析

  • 將常量池中的符號引用替換爲直接引用。
    • 符號引用:定義在Class文件中,以一組符號描述所引用的目標,與虛擬機內存佈局無關,引用的目標不一定是加載到內存中。
    • 直接引用:可以是指向目標的指針、相對偏移量、間接定位目標的句柄等,與虛擬機內存佈局相關,引用的是內存中的目標。
  • 包括類或接口、字段、類方法、接口方法的解析。

初始化

  • 執行代碼中的類構造器方法。
  • 包括靜態初始化塊、靜態變量。
  • 虛擬機會保證類構造器在多線程環境中被正確的加鎖、同步,即只有一個線程在某一時刻執行這個類的類構造器,其他線程阻塞。

類加載器

  • 兩個類相等:加載的類加載器相同,類本身相同。
  • 相同的類在不同加載器中加載,這兩個類不同,instanceof的結果爲 false 。
  • java中的類加載器繼承java.lang.ClassLoader這一抽象類。

雙親委派模型

3種系統提供的類加載器

  • 啓動類加載器。
    • 由HotSpot中由C++實現,用戶無法獲得實例。
    • 加載JAVE_HOME\lib目錄,或者-Xbootclasspath參數的路徑。
  • 擴展類加載器。
    • sun.misc.Launcher$ExtClassLoader實現。
    • 加載JAVA_HOME\lib\ext目錄,或者java.ext.dirs系統變量指定的路徑。
  • 應用程序類加載器(系統類加載器)。
    • sun.misc.Launcher$AppClassLoader實現。
    • 是ClassLoader中getSystemClassLoader()的返回值。
    • 加載用戶類路徑Classpath上的類。

委派行爲

  • 180529.classloader.png
  • 使用組合來實現父子關係。
  • 如果一個類加載器收到類加載請求,首先將請求委派給父類,父類通向如此處理。只有當父類無法處理請求的時候,子類纔會處理這個請求。所以所有的加載請求最後都會傳遞到頂層的啓動類加載器。
  • 可以使得java類和加載器一起具備了優先級的層次關係。例如java.lang.Object類最終都會由頂層的啓動類加載器加載,從而保證了唯一性。
  • java.lang.ClassLoader的核心方法。
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //父類拋出異常,表示父類無法完成加載請求
                }

                if (c == null) {
                    //父類無法加載,調用自身的findClass方法加載
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

委派模型的破壞

線程上下文類加載器

  • 基礎類需要調用價更加具體的代碼。如JNDI服務,代碼由啓動類加載器加載,但是需要調用應用程序類加載器的類提供的接口。
  • 線程上下文類加載器可以使用父類加載器請求子類加載器去完成類加載。

OSGI熱部署

  • 實現模塊化熱部署,每一個模塊(Bundle)都有自己的類加載器。
  • 類加載器發展爲網絡結構。
    180529.osgi.png
  • 收到類加載請求之後的操作:
    1. 將以java.*開頭的類委派給父類加載器。
    2. 否則,將委派列表名單的類委派給父類加載器。
    3. 否則,將Import列表中的類委派給Export這個類的Bundle的類加載器加載。
    4. 否則,查找當前Bundle的ClassPath,使用自己的類加載器加載。
    5. 否則,查找類是否在自己的Fragment Bundle中,如果在,委派給Fragment Bundle的類加載器加載。
    6. 否則,查找Dynamic Import列表的Bundle,委派給對應的Bundle的類加載器加載。
    7. 否則,查找失敗。

自定義類加載器

  • 需要繼承抽象類ClassLoader,覆蓋findClass()方法,自定義讀取class文件的方法,並調用definClass()這一抽象類中的方法。
    • 不建議覆蓋loadClass()方法,這樣破壞委派模型。
public class MyClassLoader extends ClassLoader {

    private String classpath;

    private String fileType = ".class";

    public MyClassLoader(String classpath) {
        super();
        this.classpath = classpath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = getData(name);
        return defineClass(name, data, 0, data.length);
    }

    private byte[] getData(String className) {
        InputStream in = null;
        ByteArrayOutputStream out = null;

        String path = getFilePath(className);
        byte[] data = null;

        try {
            in = new FileInputStream(path);
            out = new ByteArrayOutputStream();
            int c = 0;
            while (-1 != (c = in.read())) {
                out.write(c);
            }
            data = out.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return data;

    }

    private String getFilePath(String className) {
        return classpath + "\\" + className.replace('.', '\\') + fileType;
    }
}
  • 需要加載的類和測試代碼如下:
    • 注意加載的類的package名稱和路徑需要一致否則會報 NoClassDefFoundError
package hjg.hjg;

public class LoaderTest {
    public LoaderTest() {
    }

    public void say() {
        System.out.println("hello!!!");
    }
}
public class TestClassLoader {

    public static void main(String[] args)
            throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        MyClassLoader classLoader = new MyClassLoader("A:");
        Class<?> loadTest = classLoader.loadClass("hjg.hjg.LoaderTest");

        Object object = loadTest.newInstance();
        Method method = loadTest.getMethod("say", null);
        method.invoke(object, null);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章