Class檔案的載入流程如下圖所示,其中載入、驗證、準備、初始化、和卸載的順序是固定的,解析有可能會在初始化之後進行。
類裝載的條件
Class只有被使用到的時候才會被裝載,一個類或介面在第一次主動使用時,必須要進行初始化。主動使用包括以下幾種情況:
當建立一個類的執行個體時,比如使用new關鍵字,或者通過反射、複製、還原序列化;
當調用類的靜態方法時,也就是使用了invokestatic指令;
當使用類或介面的靜態欄位(非final的常量),比如使用getstatic或putstatic指令;
當使用java.lang.reflect包中的方法,反射類的方法時;
當初始化子類時,首先要先初始化父類;
主函數的入口(main()函數);
下面看一個被動引用的例子:
/** * 被動使用類欄位示範:通過子類引用父類的靜態欄位,不會導致子類初始化 * * @author xuefeihu * */public class SuperClass {static {System.out.println("SuperClass init!");}public static int value = 123;}public class SubClass extends SuperClass {static {System.out.println("SubClass init!");}}public class NotInitialization {public static void main(String[] args) {System.out.println(SubClass.value);}}
運行後的結果為:
SuperClass init!123
下面看一個被動引用的例子:
// 載入Child之前會去先載入Parentpublic class Parent {static {System.out.println("Parent init");}}public class Child extends Parent {static {System.out.println("Child init");}}public class InitMain {public static void main(String[] args) {Child c = new Child();}}
執行結果如下:
Parent initChild init
載入
在類載入階段主要完成以下三件事情:①通過此類的全限定名來擷取此類的二進位位元組流。②將這個位元組流所代表的靜態儲存結構轉化為方法區的運行時資料結構。③在java堆中產生對應的java.lang.Class對象。java位元組碼的擷取方式可以有以下幾種方式:
(1)從ZIP包中擷取(如jar、ear、war等)
(2)從網路中擷取
(3)運行時動態產生,使用最多的就是動態代理(如:JDK動態代理、CGLIB動態代理)
(4)其他檔案產生(如JSP編譯產生對應的class檔案)
(5)從資料庫中讀取(相對較少)
驗證
驗證的目的是保證位元組碼檔案是正確的。驗證的方式主要有以下幾種:①檔案格式的驗證;②中繼資料驗證;③位元組碼驗證(很複雜);④符號引用驗證
(1)檔案格式的驗證
①是否以0XCAFEBABY開頭
②主次版本號碼是否在當前JVM處理範圍之內
③常量池中是否有不被支援的常量
......
(2)中繼資料驗證
①是否有父類
②繼承了final類。
③非抽象類別實現了所有的抽象方法
......
(3)位元組碼驗證
①運行檢查
②棧資料類型和作業碼資料參數吻合
③跳轉指令指定到合理的位置
......
(4)符號引用驗證
①符號引用中通過字串描述的許可權定名是否能找到對應的類
②類中是否存在符合方法的欄位描述以及簡單名稱所描述的方法和欄位
③符號引用中的類、欄位、方法的訪問性是否可以被當前類訪問
......
準備
準備階段就是為static類型的變數分配初始值(如int類型的初始值為0),需要注意的是當變數類型為static final時,準備階段就將其賦值為最終的值。
解析
JVM將常量池內的符號引用(與JVM無關)轉化為直接引用(與JVM相關)。解析動作主要針對於類或介面、欄位、類方法、介面方法、方法類型、方法控制代碼和調用點限定符7類符號引用。
初始化
初始化階段包括執行構造器,其中包括了static變數指派陳述式和static{}靜態塊;子類的調用之前保證父類的被調用;還有一點就是是安全執行緒的。
參考:《深入理解Java虛擬機器》
連結:http://moguhu.com/article/detail?articleId=50