1. 程式人生 > >深入理解JVM(七)JVM類載入機制

深入理解JVM(七)JVM類載入機制

7.1JVM類載入機制

  虛擬機器把資料從Class檔案載入到記憶體,並且校驗、轉換解析和初始化最終形成可以被虛擬機器使用的Java型別,這就是虛擬機器的類載入機制。

7.2類載入的時機

  1.類載入的步驟開始的順序: 載入(Loading) -> 驗證(Verification) -> 準備(Preparation) -> 解析(Resolution) -> 初始化(Initialization) -> 使用(Using) -> 解除安裝(Unloading)

 ,驗證、準備、解析的過程稱為 連結 ,而載入、驗證、準備、初始化和解除安裝的執行的開始順序是確定的,而解析可能在初始化之後開始;

  2.關於初始化階段,有5種情況需要立即進行初始化:

    (1)遇到這四個位元組碼指令時:new、getstatic、putstatic、invokestatic,如果類未進行過初始化,那麼進行初始化,這幾個位元組碼指令所在場景:new物件、呼叫類的靜態屬性、呼叫類的靜態方法;

    (2)使用 java.lang.reflect 包的方法對類進行反射呼叫時,如果未進行過初始化,那麼進行初始化;

    (3)當初始化一個類時,其父類未進行初始化時,對其父類進行初始化;

    (4)虛擬機器啟動時需要初始化一個指定的主類(包含main()方法的類);

    (5)使用jdk1.7, java.lang.invoke.MethodHandle 例項的解析結果為 REF_getStatic 、 REF_putStatic 、 REF_invokeStatic 的方法控制代碼,如果此控制代碼對應的類未初始化,那麼進行初始化。

  3.只有以上五種情況類才會被初始化,它們也叫 主動引用 ,而其他的引用類的方式則不會初始化類,它們叫做 被動引用 :

    (1)使用子類訪問父類的靜態屬性時,不會初始化子類;

    (2)建立一個類的陣列時,不會初始化此類,但是會初始化出一個另外的類,代表這個陣列物件;

    (3)訪問一個類的靜態常量時,不會初始化這個類,這個靜態常量進入常量池會被歸屬給NonInitialization類的常量池中,代表不會初始化;

7.3類載入的過程

  1.載入:

    (1)通過類的全限定名來獲取Class檔案的二進位制流;

    (2)將這個位元組流根據虛擬機器所需要的儲存結構存放在記憶體中;

    (3)生成這個類的 java.lang.Class 物件,作為訪問類的資料的外部介面;

  2.驗證:

    雖然Java程式碼的編譯過程不允許一些不安全的做法(C/C++常做),比如:訪問陣列邊界以外的資料、錯誤的物件轉型、跳轉到不存在的行,但是不能保證Class檔案不被修改,所以驗證這一步驟作為連線的第一個階段對於JVM的安全性來說非常重要;

    驗證的過程又分為四個階段: 檔案格式驗證 、 元資料驗證 、 位元組碼驗證 、 符號引用驗證 

    (1)檔案格式驗證:①驗證魔數;②主、次版本號;③長度檢查;等等

    (2)元資料驗證:①驗證除 java.lang.Object 類以外其他類有無繼承父類;②是否繼承了final類;③非抽象類是否重寫了必須重寫的方法;④欄位、方法和父類是否矛盾;等等

    (3)位元組碼驗證:①驗證錯誤的物件轉型;②跳轉到不存在的行;等等

    (4)符號引用驗證:①驗證能不能通過符號引用的類的全限定名找到這個類;②驗證這個符號引用的類、欄位和方法的可訪問性(public、private);等等,驗證完成之後,則符號引用轉化為 直接引用 (記憶體地址引用);

  3.準備:

     準備階段是為類變數(static修飾)分配記憶體並賦予初始值的階段;

    (1)這裡說的賦予初始值說的一般都是零值,比如: public static int i = 1 這裡的 i 在準備階段完成之後會被賦值為0而非1,賦值為1那是初始化階段;

    (2) public static final int i = 1; 而這裡的 i在準備階段之後賦值為1;

  4.解析:

    將常量池內的 符號引用 轉換為 直接引用 的過程,分為 類或介面解析 、 欄位解析 、 類方法解析 、 介面方法解析 。

  5.初始化:

    初始化階段才算是真正開始執行Java程式碼,初始化階段就是執行類構造器的 <cinit>() 方法,對應 static{} 方法塊;

    (1)<cinit>()方法只能訪問到static{}程式碼塊前面的變數,而在static後面的變數,只能賦值,不能訪問引用(非法向前引用);

    (2)子類初始化,會預設先初始化父類來呼叫父類的<cinit>()方法;

    (3)一般類中沒有static{}程式碼塊,那麼也就不會生成<cinit>()方法;

    (4)介面不能寫static{}程式碼塊,但是賦值給變數時也會生成<cinit>()方法;

    (5)<cinit>()方法是同步的,執行緒安全的。

7.4類載入器

   類載入器 作用於 載入 階段中,根據類的全限定名來獲取Class檔案的二進位制流。

  1.關於類和類載入器:

    兩個類要相等( equals() ),它們首先要是同一個類載入器進行載入的;

  2.雙親委派模型:

    (1)三種系統的類載入器:

      ①啟動類載入器(Bootstrap ClassLoader):載入 %JAVA_HOME%/lib 下和 -Xbootclasspath 指定的目錄下的類庫;

      ②擴充套件類載入器(Extension ClassLoader):載入 %JAVA_HOME%/lib/ext 目錄下和 java.ext.dirs 系統變數所指定的目錄下的類庫;

      ③ 應用程式類載入器(Application ClassLoader):載入使用者類路徑下的類庫;

    (2)雙親委派模型:

      如下圖所示,要求除 啟動類載入器(Bootstrap ClassLoader) 以外,其它類載入器都要有自己的父載入器,它們之間不是 繼承 關係,而是 組合 複用的關係;

      

    (3)雙親委派機制工作過程:

      一個類載入器在收到類載入的請求時,不會立即去載入這個類,而是向 父類載入器 請求載入,依次類推,直到頂層類載入器,只有當類載入器不能載入此類時才會讓 子類載入器 去載入這個類;

    (4)雙親委派機制的意義:

      如此保證了類不會因為不同類載入器導致加載出不同的類,從而使程式混亂,例如自己寫一個 java.lang.String 類,系統只加載了jdk預設的 java.lang.String 的類檔案,而不會載入自己寫的java.lang.String類;

  3.破壞雙親委派模型: