《深入理解java虛擬機》學習筆記——類加載機制

虛擬機把描述類的數據從class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型。

1、類加載的時機

類加載到虛擬機內存再直到卸載出內存,生命週期包括加載、驗證、準備、解析、初始化、使用和卸載這7個階段,其中加載、驗證、準備、初始化這五個階段是順序進行的,也就是依次開始。

什麼時候加載?虛擬機規範沒有強制約束,虛擬機可以自由實現。

什麼時候初始化?這個嚴格規定了有且只有5種情況必須對類進行初始化。

1)遇到new、getstatic、putstatic、invokestatic這4條字節碼指令時,如果類還沒有初始化就觸發初始化階段。這4條指令最常見的場景是:new關鍵字實例化對象、讀取或設置一個類的靜態字段、調用一個類的靜態方法。

2)使用reflect對類進行反射調用的時候

3)初始化一個類,但其父類還沒有初始化,則先觸發父類的初始化

4)虛擬機啓動的時候,主類(包含了main方法)的初始化

5)JDK1.7 中動態語言支持的一些情況

Tips:對於靜態字段,只有直接定義這個字段的類纔會被初始化,因此通過子類引用父類中定義的靜態字段只會觸發父類的初始化;通過數組定義來引用類,不會觸發此類的初始化,因爲數組在java中也是一種對象(觸發了數組類的初始化?);常量(static final修飾)在編譯階段會存入調用類的常量池中,本質上沒有直接引用到定義常量的類,因此也不會觸發定義常量類的初始化。

2、類加載的過程

加載階段

加載階段主要完成3件事情,1)通過類的全限定名來獲取定義此類的二進制字節流;2)將這個字節流代表的靜態存儲結構轉化爲方法區的運行時數據結構;3)在內存中生成一個代表這個類的Class對象,作爲方法區的訪問入口。

對於數組類本身不通過類加載器創建,它是由Java虛擬機直接創建的,但數組類的元素類型最終要靠類加載器去創建。

驗證階段

驗證、準備、解析一起組成了連接階段,驗證是第一步,這一階段是爲了確保Class文件的字節流包含的信息符合當前虛擬機的要求,不會危害虛擬機安全。

驗證階段大致完成4個階段的檢驗動作:

1)文件格式驗證,驗證字節流是否符合Class文件格式的規範;

2)元數據驗證,對字節碼描述的信息進行語義分析,如是否繼承了final類,是否實現了抽象方法等;

3)字節碼驗證,對數據流和控制流分析,主要是對類的方法體校驗,如保證方法體中類型轉換是有效的等;

4)符號引用驗證,如通過字符串描述的全限定名是否能找到對應的類,指定類中是否存在相應的字段和方法等,這一階段會拋出NoSuchFieldError、NoSuchMethodError這些異常。

準備階段

準備階段是正式爲類變量(被static修飾)分配內存並設置類變量初始值的階段,這些變量所使用的內存將在方法區中分配,不包括實例變量,實例變量的分配在對象實例化時候進行。另外這個階段只會初始化類變量,也就是給類變量一個默認值,具體的賦值在初始化階段(類構造器<clinit>()方法)。

解析階段

解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。

符號引用是一組符號來描述所引用的目標,其字面量形式明確定義在Java虛擬機規範的Class文件格式中;直接引用可以是直接指向目標的指針、相對偏移量或一個能間接定位到目標的句柄。

初始化

類初始化是類加載過程的最後一步。在準備階段爲類變量分配空間,在初始化階段對類變量初始化賦值,即初始化階段是執行類構造器<clinit>()方法的過程。

  • <clinit>()方法是由編譯器自動收集類中的所有變量的賦值動作和靜態語句塊(static{}塊)中的語句合併產生的,編譯器收集的順序是語句在源文件中出現的順序決定的,靜態語句塊中只能訪問到定義在靜態語句塊之前的變量,定義在它之後的變量在之前可以訪問但不能賦值;
  • <clinit>()方法與類構造函數(或者實例構造器<init>()方法)不同,它不需要顯示調用父類構造器,虛擬機會保證在子類的<clinit>()方法執行之前父類的已經執行完畢;
  • 由於父類的<clinit>()方法先執行,意味着父類中定義的靜態語句塊要優先於子類的變量賦值操作;
  • <clinit>()方法對於類或者接口不是必須的,如果一個類沒有靜態語句塊和變量的賦值操作,那編譯器就可以不爲這個類生成<clinit>()方法;
  • 虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確的加鎖、同步。

發佈了54 篇原創文章 · 獲贊 12 · 訪問量 72萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章