類的生命週期:
類從被載入到虛擬機器記憶體中開始,到卸載出記憶體結束。生命週期包括:載入、驗證、準備、解析、初始化、使用、解除安裝;其中驗證、準備、解析稱為連線。
載入、驗證、準備、初始化、解除安裝,這幾個階段的順序是確定的,類的載入過程必須按照這個順序按部就班的開始;解析階段不一定,某些情況下可以在初始化階段之後再開始。
必須立即對類進行 "初始化" 的5種情況(虛擬機器規範規定):
注意:載入、驗證、準備階段必須在此之前開始
1、遇到 new、getstatic、putstatic 或 invokestatic 這 4 條位元組碼指令時,如果沒有進行過初始化,需要先出發其初始化;
生成這4條指令的最常見的 Java 程式碼場景是:使用 new 關鍵字例項化物件的時候;讀取、設定一個類的靜態欄位(被 final 修飾,已在編譯器把結果放入常量池的靜態欄位除外)的時候;呼叫一個類的靜態方法的時候。
2、使用 java.lang.reflect 包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,需要先出發其初始化;
3、當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化;
4、當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含 main() 方法的那個類),虛擬機器會先初始化這個主類;
5、當使用 JDK 1.7 的動態語言支援時,如果一個 java.lang.invoke.MethodHandle 例項最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
這 5 中場景中的行為稱為對一個類的主動引用。另外,所有引用類的方式都不會觸發初始化,被稱為被動引用。
被動引用示例:
1、透過子類引用父類的靜態欄位,不會導致子類初始化;
public class TestNotInit {
public static void main(String[] args) {
// 透過子類引用父類的靜態欄位,不會導致子類初始化
System.out.println(SubClass.value);
}
class SuperClass {
public static int value = 500;
static {
System.out.println("super class init");
class SubClass extends SuperClass {
System.out.println("sub class init");
輸出結果如下:
super class init
500
2、透過陣列定義來引用類,不會觸發此類的初始化;
// 透過陣列定義來引用類,不會觸發此類的初始化
SuperClass[] array = new SuperClass[10];
輸出結果如下(什麼都沒有輸出):
3、常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量的類的初始化。
// 常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量的類的初始化
System.out.println(SuperClass.HELLO_WORLD);
public static final String HELLO_WORLD = "hello world";
1
hello world
備註:介面與類真正有區別的是 第3條:當一個介面在初始化時,並不要求其父介面全部都完成了初始化,只有在真正用到父介面的時候(引用介面中定義的常量)才會初始化。
類的生命週期:
類從被載入到虛擬機器記憶體中開始,到卸載出記憶體結束。生命週期包括:載入、驗證、準備、解析、初始化、使用、解除安裝;其中驗證、準備、解析稱為連線。
載入、驗證、準備、初始化、解除安裝,這幾個階段的順序是確定的,類的載入過程必須按照這個順序按部就班的開始;解析階段不一定,某些情況下可以在初始化階段之後再開始。
必須立即對類進行 "初始化" 的5種情況(虛擬機器規範規定):
注意:載入、驗證、準備階段必須在此之前開始
1、遇到 new、getstatic、putstatic 或 invokestatic 這 4 條位元組碼指令時,如果沒有進行過初始化,需要先出發其初始化;
生成這4條指令的最常見的 Java 程式碼場景是:使用 new 關鍵字例項化物件的時候;讀取、設定一個類的靜態欄位(被 final 修飾,已在編譯器把結果放入常量池的靜態欄位除外)的時候;呼叫一個類的靜態方法的時候。
2、使用 java.lang.reflect 包的方法對類進行反射呼叫的時候,如果類沒有進行過初始化,需要先出發其初始化;
3、當初始化一個類的時候,如果發現其父類還沒有進行過初始化,則需要先觸發其父類的初始化;
4、當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含 main() 方法的那個類),虛擬機器會先初始化這個主類;
5、當使用 JDK 1.7 的動態語言支援時,如果一個 java.lang.invoke.MethodHandle 例項最後的解析結果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
這 5 中場景中的行為稱為對一個類的主動引用。另外,所有引用類的方式都不會觸發初始化,被稱為被動引用。
被動引用示例:
1、透過子類引用父類的靜態欄位,不會導致子類初始化;
public class TestNotInit {
public static void main(String[] args) {
// 透過子類引用父類的靜態欄位,不會導致子類初始化
System.out.println(SubClass.value);
}
}
class SuperClass {
public static int value = 500;
static {
System.out.println("super class init");
}
}
class SubClass extends SuperClass {
static {
System.out.println("sub class init");
}
}
輸出結果如下:
super class init
500
2、透過陣列定義來引用類,不會觸發此類的初始化;
public class TestNotInit {
public static void main(String[] args) {
// 透過陣列定義來引用類,不會觸發此類的初始化
SuperClass[] array = new SuperClass[10];
}
}
class SuperClass {
public static int value = 500;
static {
System.out.println("super class init");
}
}
輸出結果如下(什麼都沒有輸出):
3、常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量的類的初始化。
public class TestNotInit {
public static void main(String[] args) {
// 常量在編譯階段會存入呼叫類的常量池中,本質上並沒有直接引用到定義常量的類,不會觸發定義常量的類的初始化
System.out.println(SuperClass.HELLO_WORLD);
}
}
class SuperClass {
public static final String HELLO_WORLD = "hello world";
static {
System.out.println("super class init");
}
}
輸出結果如下:
1
hello world
備註:介面與類真正有區別的是 第3條:當一個介面在初始化時,並不要求其父介面全部都完成了初始化,只有在真正用到父介面的時候(引用介面中定義的常量)才會初始化。