深刻了解Java虛擬機(3-1)虛擬機類加載機制

 

虛擬機類加載機制

1、類加載的階段和時機

  1.階段

      整個生命週期包括:加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)7個階段。java

      其中驗證、準備、解析3個部分統稱爲鏈接(Linking),數組

  2.類加載時機

      1)遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化。數據結構

        經常使用:使用new關鍵字實例化對象的時候、讀取或設置一個類的靜態字段(被final修飾、已在編譯期把結果放入常量池的靜態字段除外)的時候,以及調用一個類的靜態方法的時候。佈局

      2)使用java.lang.reflect包的方法對類進行反射調用的時候,若是類沒有進行過初始化,則須要先觸發其初始化。spa

      3)當初始化一個類的時候,若是發現其父類尚未進行過初始化,則須要先觸發其父類的初始化(接口除外)線程

      4)當虛擬機啓動時,用戶須要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類。翻譯

      5)當使用JDK 1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化指針

    例子code

      1.對於靜態字段,只有直接定義這個字段的類纔會被初始化,所以經過其子類來引用父類中定義的靜態字段,只會觸發父類的初始化而不會觸發子類的初始化對象

package org.fenixsoft.classloading;
/**
*被動使用類字段演示一:
*經過子類引用父類的靜態字段,不會致使子類初始化
**/
public class SuperClass{
    static{
        System.out.println("SuperClass init!");
    }
    public static int value=123;
}

public class SubClass extends SuperClass{
    static{
        System.out.println("SubClass init!");
    }
}
/*
**
*非主動使用類字段演示
**/
public class NotInitialization{
    public static void main(String[]args){
        System.out.println(SubClass.value);
    }
}

 

      2.類的數組類型

package org.fenixsoft.classloading;
/**
*被動使用類字段演示二:
*經過數組定義來引用類,不會觸發此類的初始化
*虛擬機會初始化一個[SuperClass的數組類,由虛擬機自動產生,經過執行newarray字節碼
**/
public class NotInitialization{
    public static void main(String[]args){
        SuperClass[]sca=new SuperClass[10];
    }
}    

 

      3.常量字段(static final)

package org.fenixsoft.classloading;
/**
*被動使用類字段演示三:
*常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的類的初始化。
**/
public class ConstClass{
    static{
        System.out.println("ConstClass init!");
    }
    public static final String HELLOWORLD="hello world";
}
/*
**
*非主動使用類字段演示
**/
public class NotInitialization{
    public static void main(String[]args){
        System.out.println(ConstClass.HELLOWORLD);
    }
}

 

2、類加載過程

  1.加載

      1)經過一個類的全限定名來獲取定義此類的二進制字節流。

      2)將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構。

      3)在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問入口。

    數組的加載:

      數組的元素類型是基本類型:引導類加載器

      數組的元素類型是引用類型:遞歸父類加載

  2.驗證:-Xverify:none取消

      1)文件格式驗證:驗證class文件格式,並能被當前虛擬機處理

      2)元數據驗證:字節碼描敘信息進行語義分析,如:類是否有父類,是否與父類字段衝突

      3)字節碼驗證:肯定程序的語義合乎邏輯

      4)符號引用驗證:符號引用所引用的字符串的驗證,如:全限定名可否找到對應的類,類、字段、方法的訪問性

  3.準備

    爲類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配

    注意:類變量(static)、初始值爲類型的初值,而並非「=」號賦予的值;如:int初值爲0等;可是static fianl常量除外(字段表中ConstantValue屬性指定的值)

    

 

  4.解析

      解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程

      1)符號引用(Symbolic References):符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位到目標便可。

        符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。

        各類虛擬機實現的內存佈局能夠各不相同,可是它們能接受的符號引用必須都是一致的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件格式中。

      2)直接引用(Direct References):直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。

        直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣虛擬機實例上翻譯出來的直接引用通常不會相同。

        若是有了直接引用,那引用的目標一定已經在內存中存在。

      解析的類型:類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符

      1.類或接口的解析

        假設當前代碼所處的類爲D,若是要把一個從未解析過的符號引用N解析爲一個類或接口C的直接引用,那虛擬機完成整個解析的過程須要如下3個步驟:

        1)非數組類型,那虛擬機將會把表明N的全限定名傳遞給D的類加載器去加載這個類C。

        2)數組類型,而且數組的元素類型爲對象,也就是N的描述符會是相似「[Ljava/lang/Integer」的形式,那將會按照第1點的規則加載數組元素類型。若是N的描述符如前面所假設的形式,須要加載的元素類型就是「java.lang.Integer」,接着由虛擬機生成一個表明此數組維度和元素的數組對象。

        3)若是上面的步驟沒有出現任何異常,那麼C在虛擬機中實際上已經成爲一個有效的類或接口了,但在解析完成以前還要進行符號引用驗證,確認D是否具有對C的訪問權限。若是發現不具有訪問權限,將拋出java.lang.IllegalAccessError異常。

      2.字段的解析

        本類----接口----父類遞歸----不存在拋出異常

      3.類方法解析

        解析出的符號引用不是類,拋出異常----本類----父類----接口(抽象方法拋異常)----不存在拋異常

      4.接口方法解析

        解析出非符號引用不是接口,拋異常----本接口----父接口----不存在拋出異常

  4.初始化

      初始化是執行類構造器<clinit>方法

      <clinit>()方法是由編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊(static{}塊)中的語句合併產生的

      編譯器收集的順序是由語句在源文件中出現的順序所決定的,靜態語句塊中只能訪問到定義在靜態語句塊以前的變量,定義在它以後的變量,在前面的靜態語句塊能夠賦值,可是不能訪問

 

3、類加載器

  比較兩個類是否「相等」:同一個類加載器,加載同一個類;纔是相等

  不然同一個Class文件,被同一個虛擬機加載,只要加載它們的類加載器不一樣,那這兩個類就一定不相等。

  這裏所指的「相等」,包括表明類的Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果,也包括使用instanceof關鍵字作對象所屬關係斷定等狀況。

  1.類加載器

    啓動類加載器:加載JAVA_HOME\lib下或-Xbootclasspath指定路徑下的指定類庫

    擴展類加載器:加載ext類庫,任何類庫都行

    應用程序類加載器:用戶用的最多的加載器

  2.雙親委派模型

    雙親委派模型:保證了類的相等或者說是惟一

      若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的類加載器都是如此,

      所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試自己去加載。

      

 

  3.打破雙親委派模型

    JNDI服務

      JNDI提供命名服務和目錄服務,是經過啓動類加載器加載,是對資源的管理

      可是既然是管理資源,就要加載應用程序中三方廠商提供的JNDI接口,才能對三方資源進行目錄和命名服務提供

      而啓動類加載器是沒法加載到這些應用程序中的資源

      解決:經過一個ThreadContextClassLoader線程上下文加載器加載

     OSGi

      熱部署,動態的增長和卸載模塊,原有的類加載機制就不合適了

相關文章
相關標籤/搜索