Java類載入過程 ——Thinking in Java學習筆記(六)
java中一個類從被載入開始,一直到被銷燬為止,類的整個生命週期包括:載入、驗證、準備、解析、初始化、使用和解除安裝七個階段。 其中,類載入過程包括載入、驗證、準備、解析、初始化,其中,驗證、準備、解析又被合稱為連線過程。
1、載入階段
載入過程的主要工作有:
1)通過一個類的全限定名來獲取定義此類的二進位制位元組流。
2)將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
3)在java堆中生成一個代表這個類的Class物件,作為訪問方法區中這些資料的入口。
2、驗證階段
驗證類資料資訊是否符合JVM規範,是否是一個有效的位元組碼檔案,驗證內容涵蓋了類資料資訊的格式驗證、語義分析
格式驗證:驗證是否符合class檔案規範
語義驗證:檢查一個被標記為final的型別是否包含子類;檢查一個類中的final方法是否被子類進行重寫;確保父類和子類之間沒有不相容的一些方法宣告(比如方法簽名相同,但方法的返回值不同)
操作驗證:在運算元棧中的資料必須進行正確的操作,對常量池中的各種符號引用執行驗證(通常在解析階段執行,檢查是否通過富豪引用中描述的全限定名定位到指定型別上,以及類成員資訊的訪問修飾符是否允許訪問等)
3、準備階段
這個階段正式為類變數(被static修飾的變數)分配記憶體並設定類變數初始值,這個記憶體分配是發生在方法區中。
1、注意這裡並沒有對例項變數進行記憶體分配,例項變數將會在物件例項化時隨著物件一起分配在JAVA堆中。
2、這裡設定的初始值,通常是指資料型別的零值(預設值)。
private static int a = 3;
這個類變數a在準備階段後的值是0,將3賦值給變數a是發生在初始化階段。
4、解析階段
將常量池中的符號引用轉為直接引用(得到類或者欄位、方法在記憶體中的指標或者偏移量,以便直接呼叫該方法),這個可以在初始化之後再執行。 可以認為是一些靜態繫結的會被解析,動態繫結則只會在執行是進行解析;靜態繫結包括一些final方法(不可以重寫),static方法(只會屬於當前類),構造器(不會被重寫)
5、初始化階段
初始化是類載入機制的最後一步,這個時候才正真開始執行類中定義的JAVA程式程式碼。在前面準備階段,類變數已經賦過一次系統要求的初始值,在初始化階段最重要的事情就是對類變數進行初始化
java類中對類變數指定初始值有兩種方式:1、宣告類變數時指定初始值;2、使用靜態初始化塊為類變數指定初始值。
初始化的時機
1)建立類例項的時候,分別有:1、使用new關鍵字建立例項;2、通過反射建立例項;3、通過反序列化方式建立例項。
new Test();
Class.forName(“com.mengdd.Test”);
2)呼叫某個類的類方法(靜態方法)
Test.doSomething();
3)訪問某個類或介面的類變數,或為該類變數賦值。
int b=Test.a;
Test.a=b;
4)初始化某個類的子類。當初始化子類的時候,該子類的所有父類都會被初始化。
5)直接使用java.exe命令來執行某個主類。
除了上面幾種方式會自動初始化一個類,其他訪問類的方式都不會觸發類的初始化,稱為被動引用。
1、子類引用父類的靜態變數,不會導致子類初始化。
// 父類
public class SupClass {
public static int a = 123;
static {
System.out.println("supclass init");
}
}
// 子類
public class SubClass extends SupClass {
static {
System.out.println("subclass init");
}
}
// 測試類
public class Test {
public static void main(String[] args) {
System.out.println(SubClass.a);
}
}
執行結果:
supclass init
123
2、通過陣列定義引用類,不會觸發此類的初始化
public class SupClass {
public static int a = 123;
static {
System.out.println("supclass init");
}
}
public class Test {
public static void main(String[] args) {
SupClass[] spc = new SupClass[10];
}
}
執行結果:
空
3、引用常量時,不會觸發該類的初始化(這裡指的是被 public static final修飾的常量)
public class ConstClass {
public static final String A= "MIGU";
static {
System.out.println("ConstCLass init");
}
}
public class TestMain {
public static void main(String[] args) {
System.out.println(ConstClass.A);
}
}
執行結果:
MIGU
用final修飾某個類變數時,它的值在編譯時就已經確定好放入常量池了,所以在訪問該類變數時,等於直接從常量池中獲取,並沒有初始化該類。
初始化的步驟
1、如果該類還沒有載入和連線,則程式先載入該類並連線。
2、如果該類的直接父類沒有載入,則先初始化其直接父類。
3、如果類中有初始化語句,則系統依次執行這些初始化語句。
在第二個步驟中,如果直接父類又有直接父類,則系統會再次重複這三個步驟來初始化這個父類,依次類推,JVM最先初始化的總是java.lang.Object類。當程式主動使用任何一個類時,系統會保證該類以及所有的父類都會被初始化。
在第三個步驟中,系統執行初始化語句的順序為:
- 父類靜態成員和靜態初始化塊
- 子類靜態成員和靜態初始化塊
- 父類的例項成員和例項初始化塊
- 執行父類的構造方法
- 子類例項成員和例項初始化塊
- 執行子類的構造方法