《Understanding the JVM》讀書筆記之四——類加載機制

類的生命週期

 

其中,加載、驗證、準備、初始化、卸載5個階段都是順序開始(但不一定是順序結束)。而解析階段則不一定,某些情況可以在初始化階段之後再開始。

 

類加載過程

1. 加載(加載階段與連接階段的部分內容是交叉進行的)
  • 加載階段,虛擬機需要完成3件事:
    a. 通過類的全名獲取定義此類的二進制字節流;
    b. 將字節流中的二進制靜態存儲結構轉化爲方法區的運行時數據;
    c. 在內存中生成代表這個類的java.lang.Class對象,作爲這個類的訪問入口。
  • 加載階段完成後,二進制字節流會按JVM所需要的格式存儲在方法區之中,在HotSpot虛擬機中,Class對象也存放在方法區。
  • 對於非數組類,加載階段可以通過系統提供的引導類加載器完成,也可以由用戶自定義的類加載器完成,可以通過重寫類加載器的loadClass()方法,來控制字節流獲取方式。
  • 對於數組類,數組本身通過JVM直接創建,不會通過類加載器來完成,但數組的元素類型時需要類加載器創建的。數組類的創建過程:
    a. 如果是引用類型數組:使用一般加載過程加載這個類型,該類型的加載器的類名稱空間將標識出這是個數組;
    b. 如果不是引用類型數組,JVM把數組C標記爲引導類加載器關聯;
    c. 數組的可見性和組件類型的可見性一致,非引用類型的可見性爲public

2. 驗證(驗證階段不是不要的,可以通過參數-Xverify:none關閉大部分的驗證措施)
  • 這是連接階段的第一步,目的是確保Class文件的字節流信息符合當前虛擬機的要求,並且不會危及JVM自身的安全。
  • 主要包括4種不同的校驗動作:
    a. 文件格式驗證
    b. 元數據驗證
    c. 字節碼驗證
    d. 符號引用驗證
  • 文件格式驗證——驗證字節流是否符合Class文件格式的規範,並能被檔期那版本的虛擬機處理。只有通過了這一階段的驗證字節流纔會存儲到方法區中,所以後邊的3個驗證全部都是基於方法區的存儲結構進行的,不會再直接操作字節碼。驗證點包括:
    a. 是否以魔數0xCAFEBABE開頭
    b. 主、次版本號是否在當前JVM處理範圍之內
    c. 常量池中的常量是否有不被支持的類型
    d. ……
  • 元數據驗證——對類的元數據進行驗證,保證不存在不符合Java語言規範的元數據信息。主要驗證點包括:
    a. 此類是否有父類
    b. 此類是否繼承了final類
    c. 若此類非抽象,是否實現了父類或父接口中要求實現的所有方法
    d. ……
  • 字節碼驗證——對類的方法進行校驗分析,保證被校驗的類方法在運行時不會做出危害JVM安全的事件。驗證點包括:
    a. 保證任意時刻操作數棧的數據類型與指令代碼序列都能配合工作
    b. 保證跳轉指令不會跳轉到方法體以外的字節碼指令上
    c. ……
  • ***jdk1.6對字節碼校驗進行了優化:給方法體的Code屬性的屬性表中添加了一個“StackMapTable”屬性,此屬性描述了方法體中所有基本塊開始時本地變量表和操作棧應有的狀態,在字節碼驗證期間,只需要檢查StackMapTable屬性中的記錄是否合法即可。
  • 符號引用驗證——對類自身意外的信息進行校驗,通常的校驗內容包括:
    a. 符號引用中通過字符串描述的全限定名是否能找到對應的類
    b. 指定類中是否存在符合方法的字段描述符以及簡單名稱所描述的方法和字段

3. 準備
  • 在這一階段正式爲類分配內存(在方法區中),並設置類變量的初始值。初始值一般情況下爲0或null,靜態常量會被設置爲程序代碼設定的值。
4. 解析——JVM將常量池內的符號引用替換爲直接引用的過程
  • 符號引用和直接引用:
    a. 符號引用以一組符號來描述所引用的目標,與JVM的內存佈局無關,符號引用的目標可以還不存在,符號引用的字面量形式在Java虛擬機規範中已經明確定義。
    b. 可以是直接指向目標的指針、相對偏移量、間接定位到的引用,和JVM的內存佈局息息相關,如果時直接引用,目標必須在內存中存子。
  • 解析動作主要針對:類或接口、字段、類方法、接口方法、方法類型、方法句柄、調用點限定符7類符號。
  • 接口或類的解析——如果一個從未解析過的符號引用N被解析爲一個類或接口C,則整個解析過程如下(假設當前代碼所處位置在D類中):
    a. 如果C不是數組類型:把N的全限定名傳遞給D的類加載器,去加載C,這時可能會觸發其他類的加載動作。若此過程中發現異常,則解析失敗。
    b. 如果C時數組類型,且元素爲引用類型,首先按a過程解析元素的類型,然後JVM生成一個代表此數組維度和元素的數組對象。
    c. 如果上述步驟沒有任何異常,會進行符號引用驗證,確認D是否具備對C的訪問權限,如果不具備訪問權限,拋出IllegalAcessError異常。
  • 字段的解析——字段的解析首先會對字段所屬的類型或接口的符號引用進行解析,成功之後按一下步驟進行查找(字段所屬的接口或類用C表示):
    a. 如果C本身存在簡單名稱和字段描述都與目標相匹配的字段(屬性),返回這個字段的直接引用,結束。
    b. 若a沒有,如果C實現了接口,按照繼承關係從下往上遞歸搜索接口和它的父接口,如果接口中存在簡單名稱和字段描述與目標匹配的字段,返回,結束。
    c. 如果b沒有,且C不是Object類,按繼承關係從下往上遞歸搜索父類,如果父類中存在簡單名稱和字段描述與目標匹配的字段,返回,結束。
    d. 否則,查找失敗,拋出NoSuchFieldEror異常。
    e. 查找結束後,會對這個字段進行權限驗證,如果發現不具備對這個字段的訪問權限,拋出IllegalAccessError異常。
  • 類方法解析——需要先解析類方法表的class_index項中索引的方法所屬的類或接口的符號引用,如果解析成功,按以下步驟進行類方法搜索(所屬的類用C表示):
    a. 如果在類方法表中發現class_index索引的C是個接口,直接拋出IncompatibleClassChangeError異常。
    b. 如果通過了a,在類C中查找是否有簡單名稱和描述符都與目標相匹配的方法,如果有,返回這個方法的直接引用,結束。
    c. 否則,在父類中查找
    d. 否則,在C實現的接口列表和他們的父接口列表遞歸查找,如果存在匹配的方法,說明C是一個抽象類,拋出AbstractMethodError異常。
    e. 否則,宣告失敗,拋出NoSuchMethodError異常。
    f. 如果以上過程成功返回了直接引用,對這個方法進行權限驗證。
  • 接口方法解析——首先解析接口方法表的class_index項中索引的方法所屬的類或接口類型符號引用,如果成功,按如下步驟進行搜索(C表示這個接口):
    a. 如果發現C是個類而不是接口,直接拋出IncompatibleClassChangeError異常。
    b. 若a通過,在C中查找是否存在簡單名稱和描述符都與目標想匹配的方法,有,返回這個方法的直接引用,查找結束。
    c. 否則,在C的父接口中遞歸查找,知道Object類,查找……,有,返回,結束。
    d. 否則宣告失敗,拋出NoSuchMethodError異常。
    e. 由於接口方法都時public的,所以不存在權限訪問問題。

5. 初始化——此階段開始真正執行類中定義的java代碼
  • 初始化階段時執行類構造器的<clinit>()方法的過程:
    a. <clinit>()方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態語句塊中的語句合併產生的,靜態語句塊只能訪問到定義到它之前的變量,之後的變量,靜態語句塊可以賦值,但不能訪問。
    b. <clinit>()方法不同於構造方法,不需要先調用父類的<clinit>()方法,JVM會保證父類的<clinit>()方法先執行。
    c. 如果一個類中沒有靜態語句塊,也沒有變量的複製操作,編譯器不會生產<clinit>()方法。
    d. 接口的<clinit>()方法和父接口的<clinit>()方法沒有關係,也沒有執行的先後順序,接口的實現類的<clinit>()方法執行時也不必先執行接口的<clinit>()方法。
    e. JVM會保證<clinit>()方法的線程安全。

 

類初始化

虛擬機規範嚴格規定了類初始化的情況:(有且僅有5種):

1. 遇到new、getstatic、putstatic、invokestatic四條指令時,如果沒有進行過類初始化;(四條指令依次對應:new對象,獲取、設置靜態字段(非final),調用靜態方法)

2. 使用java.lang.reflect包中的方法進行反射調用時,如果類沒有被初始化;

3. 類初始化時,如果其父類沒有被初始化,需要先初始化父類;

4. 虛擬機啓動時會先初始化main()方法所在的類;

5. 動態語言支持:如果一個java.lang.invoke.MethodHandle實例的方法引用,這個方法所在的類沒有被初始化。

***接口的初始化過程:與類的初始化過程略有不同,主要表現在第3中情況。當一個接口要初始化時,並不需要它的父接口也進行過初始化,只有在父接口真正需要的時候纔會被初始化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章