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

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

同步 擴展 父類 cin ssl 都是 mage java類型 java類

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_getStaticREF_putStaticREF_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.破壞雙親委派模型:

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