深入理解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
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類加載機制