背景
- 進(jìn)入正題前先說下背景,本人最近在了解一些JVM的知識,本文適合JVM初學(xué)者肆氓。本文完整代碼鏈接見文末。
注:本著有目的學(xué)習(xí)的宗旨亚脆,本人在了解和涉獵JVM的過程中做院,對于一些JVM規(guī)范和指令大多是帶過,遠(yuǎn)遠(yuǎn)達(dá)不到對JVM熟悉的地步濒持。
-
OK键耕,啥也不說,上來先丟兩個名詞先柑营。在類的初始化過程中屈雄,對類的引用可分為兩大類:
- 主動引用
- 被動引用
為了配合后續(xù)內(nèi)容,先上四個非常簡單的類官套,這幾個類看不懂就別往下看了酒奶,因為你的Java基礎(chǔ)還在來的路上:
public class SuperClass {
static {
System.out.println("SuperClass init!");
}
/**
* 類靜態(tài)字段
*/
public static int value = 123;
}
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init!");
}
}
public class ConstantsClass {
static {
System.out.println("ConstantsClass init!");
}
/**
* 類常量
*/
public static final int VALUE = 123;
}
public class MethodHandleClass {
static {
System.out.println("MethodHandleClass init");
}
public static void testMethodHandle(String str) {
System.out.println(str);
}
}
主動引用
- 類的初始化階段,虛擬機規(guī)范嚴(yán)格規(guī)定了有且只有以下5種情況必須立即對類進(jìn)行“初始化”奶赔,我們把這5種場景中的行為惋嚎,稱為對一個類進(jìn)行主動引用。
1. 遇到new站刑、getstatic另伍、putstatic和invokestaic這4條字節(jié)碼指令
-
遇到new、getstatic绞旅、putstatic和invokestaic這4條字節(jié)碼指令時摆尝,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)類的初始化因悲。
- new:創(chuàng)建類實例的指令(注意堕汞,與創(chuàng)建數(shù)組的指令不一樣——newarray、anewarray晃琳、multianewarray)
- getstatic讯检、putstatic:訪問類字段的指令(static字段)
- invokestatic:調(diào)用類方法的指令(static方法)
public class Initialization1 {
public static void main(String[] args) {
// 遇到new字節(jié)碼指令時,如果類沒有進(jìn)行過初始化卫旱,則需要先觸發(fā)類的初始化
new SuperClass();
}
}
// 輸出結(jié)果為
SuperClass init!
2. 反射調(diào)用
- 對類進(jìn)行反射調(diào)用的時候视哑,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化
public class Initialization2 {
public static void main(String[] args) {
try {
// 對類進(jìn)行反射調(diào)用的時候誊涯,如果類沒有進(jìn)行過初始化挡毅,則需要先觸發(fā)其初始化
Class.forName("com.xpleemoon.classinit.SuperClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
// 輸出結(jié)果為
SuperClass init!
3. 父類未初始化
- 當(dāng)初始化一個類的時候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化暴构,則需要觸發(fā)其父類的初始化
public class Initialization3 {
public static void main(String[] args) {
// 當(dāng)初始化一個類的時候跪呈,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化段磨,則需要觸發(fā)其父類的初始化
new SubClass();
}
}
// 輸出結(jié)果為
SuperClass init!
SubClass init!
4. 執(zhí)行的主類
- 當(dāng)虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類耗绿,虛擬機會先初始化這個主類(包含main()方法的一個執(zhí)行類)
public class Initialization4 {
static {
System.out.println("當(dāng)虛擬機啟動時苹支,用戶需要指定一個要執(zhí)行的主類,虛擬機會先初始化這個主類");
}
public static void main(String[] args) {
}
}
// 輸出結(jié)果為
當(dāng)虛擬機啟動時误阻,用戶需要指定一個要執(zhí)行的主類债蜜,虛擬機會先初始化這個主類
5. 使用JDK 1.7 的動態(tài)語言支持
- 當(dāng)使用JDK 1.7 的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的結(jié)果是REF_getStatic究反、REF_putStatic寻定、REF_invokeStatic的方法句柄,并且這個方法句柄對應(yīng)的類沒有進(jìn)行過初始化精耐,則需要先觸發(fā)其初始化狼速。
public class Initialization5 {
public static void main(String[] args) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
MethodHandle testMethodHandle = lookup.findStatic(MethodHandleClass.class, "testMethodHandle", MethodType.methodType(void.class, String.class));
try {
testMethodHandle.invoke("當(dāng)使用JDK 1.7 的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的結(jié)果是\n" +
"REF_getStatic卦停、REF_putStatic向胡、REF_invokeStatic的方法句柄,并且這個方法句柄對應(yīng)的類沒有進(jìn)行過初始化惊完,則需要先觸發(fā)其初始化");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
// 輸出結(jié)果為
MethodHandleClass init
當(dāng)使用JDK 1.7 的動態(tài)語言支持時僵芹,如果一個java.lang.invoke.MethodHandle實例最后的結(jié)果是
REF_getStatic、REF_putStatic小槐、REF_invokeStatic的方法句柄淮捆,并且這個方法句柄對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化
被動引用
- 除主動引用(上述5中場景)之外本股,其他所有引用類的方式都不會觸發(fā)初始化,這些其他類的引用方式稱為被動引用桐腌。下面用三個例子來說明:
例子1——通過子類引用父類的靜態(tài)字段拄显,不會導(dǎo)致子類初始化
public class NotInitialization1 {
public static void main(String[] args) {
// 子類引用父類的靜態(tài)字段,不會導(dǎo)致子類初始化
System.out.println(SubClass.value);
}
}
// 輸出結(jié)果為
SuperClass init!
123
例子2——通過數(shù)組定義來引用類案站,不會觸發(fā)此類的初始化
public class NotInitialization2 {
public static void main(String[] args) {
// 注意躬审,這里的字節(jié)碼指令是newarray而非new,那么這段代碼的初始化就是類[SuperClass的初始化
SuperClass[] sca = new SuperClass[10];
}
}
// 輸出結(jié)果為無
例子3——常量在編譯階段會存入調(diào)用類的常量池中蟆盐,本質(zhì)上沒有直接引用到定義常量的類承边,因此不會觸發(fā)
定義常量的類的初始化
public class NotInitialization3 {
public static void main(String[] args) {
// 常量在編譯階段會存入調(diào)用類的常量池中,本質(zhì)上沒有直接引用到定義常量的類石挂,
// 因此不會觸發(fā)定義常量的類的初始化
System.out.println(ConstantsClass.VALUE);
}
}
// 輸出結(jié)果為
123
- 為了實驗本文內(nèi)容博助,請猛點代碼倉庫鏈接
- 參考書目——周志明《深入理解Java虛擬機——JVM高級特性與最佳實踐》