深入理解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.破壞雙親委派模型: