說到Java虛擬機的類加載機制,很多朋友第一反應想到的應該就是ClassLoader,我也如此,不過ClassLoader其實只是Java虛擬機加載機制中的一部分,最近在看《深入理解Java虛擬機》,對Java虛擬機的類加載機制有了更深入的了解,不吐不快。
JVM中類的整個生命周期如下:
加載=》驗證=》准備=》解析=》初始化=》使用=》卸載
使用和卸載這兩個步驟不在今天的討論范圍之內,今天我們將著重討論一下前5個步驟,也就是JVM中類的整個加載機制。
1. 加載
類的加載階段,主要是獲取定義此類的二進制字節流,並將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構,最後在Java堆中生成一個代表這個類的Java.lang.Class對象作為方法區這些數據的訪問入口。
相對於類加載過程的其他階段,加載階段是開發期可控性最強的階段。我們可以通過定制不通的類加載器,也就是ClassLoader來控制二進制字節流的獲取方式。
關於ClassLoader的介紹,請參照 了解ClassLoader
2. 驗證
驗證,准備和解析其實都屬於連接階段,而驗證就是連接階段的第一步。這一階段主要是為了確保Class文件的字節流中包含的信息復合當前虛擬機的要求,並且不會危害虛擬機自身的安全。
主要驗證過程包括:文件格式驗證,元數據驗證,字節碼驗證以及符號引用驗證。
3. 准備
准備階段正式為類變量分配內存並設置初始值。這裡的初始值並不是初始化的值,而是數據類型的默認零值。這裡提到的類變量是被static修飾的變量,而不是實例變量。
關於准備階段為類變量設置零值的唯一例外就是當這個類變量同時也被final修飾,那麼在編譯時,就會直接為這個常量賦上目標值。
4. 解析
解析時虛擬機將常量池中的符號引用替換為直接引用的過程。
5. 初始化
在准備階段,變量已經賦過一次系統要求的初始值,在初始化階段,則是根據程序員通過程序的主觀計劃區初始化類變量和其他資源。
Java虛擬機規范規定了有4種情況必須立即對類進行初始化(加載,驗證,准備必須在此之前完成)
1)當使用new關鍵字實例化對象時,當讀取或者設置一個類的靜態字段(被final修飾的除外)時,以及當調用一個類的靜態方法時,如果類未初始化,則需先初始化。
2)通過反射機制對類進行調用時,如果類未初始化,則需先初始化。
3)當初始化一個類時,如果其父類未初始化,先初始化父類
4)用戶指定的執行主類(含main方法的那個類)在虛擬機啟動時會先被初始化
除了上面這4種方式,所有引用類的方式都不會觸發初始化,稱為被動引用。如:通過子類引用父類的靜態字段,不會導致子類初始化;通過數組定義來引用類,不會觸發此類的初始化;引用類的靜態常量不會觸發定義常量的類的初始化,因為常量在編譯階段已經被放到常量池中了。
總 結:
在上述5個過程當中,驗證,准備和解析完全由Java虛擬機主導和控制。只要加載階段和初始化階段程序員可以進行控制。在加載階段可以通過實現自定義的ClassLoader來加載類的二進制流,在初始化階段程序員則可完全按照需求來為類變量賦值。