1. 概述
類從被加載到虛擬機內存中開始拴竹,到卸載出內存為止唐础,它的整個生命周期包括:加載(Loading)箱歧、驗證(Verification)、準備(Preparation)一膨、解析(Resolution)呀邢、初始化(Initialization)、使用(Useing)豹绪、卸載(Unloading)7個階段价淌。其中驗證、準備和解析3個部分統(tǒng)稱為連接(Linking)瞒津,這7個階段的發(fā)生順序如圖所示蝉衣。
加載、驗證巷蚪、準備病毡、初始化和卸載這5個階段的順序是確定的,類的加載過程
必須按照這種順序按部就班地開始屁柏,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始啦膜,這是為了支持Java語言的運行時綁定(也稱為動態(tài)綁定或晚期綁定) 。
對于初始化階段淌喻,虛擬機規(guī)范則是嚴格規(guī)定了有且只有5種情況必須立即對類進行“初始化”( 而加載僧家、 驗證、 準備自然需要在此之前開始):
- 遇到new裸删、getstatic八拱、putstatic或invokestatic這4條字節(jié)碼指令時,如果類沒有進行過初始化涯塔,則需要先觸發(fā)其初始化肌稻。
- 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化伤塌,則需要先觸發(fā)其初始化。
- 當初始化一個類的時候轧铁,如果發(fā)現(xiàn)其父類還沒有進行過初始化每聪,則需要先觸發(fā)其父類的初始化。
- 當虛擬機啟動時,用戶需要指定一個要執(zhí)行的主類( 包含main()方法的那個類)药薯,虛擬機會先初始化這個主類绑洛。
- 當使用JDK 1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getStatic童本、REF_putStatic真屯、REF_invokeStatic的方法句柄, 并且這個方法句柄所對應的類沒有進行過初始化穷娱, 則需要先觸發(fā)其初始化绑蔫。
這五種情況稱為對一個類進行主動引用
。其余情況都不會觸發(fā)初始化泵额,稱為被動引用
配深。
2 主動引用
首先準備兩個類用戶測試其是否初始化。
SuperClass
package cn.lastwhisper.jvm.classloading.initiative;
/**
* @author lastwhisper
*/
public class SuperClass {
public static int value = 123;
static {
System.out.println("SuperClass static code init!");
}
public SuperClass() {
System.out.println("SuperClass constructor init! ");
}
public static int getValue() {
return value;
}
public static void setValue(int value) {
SuperClass.value = value;
}
}
SubClass 繼承自SuperClass
package cn.lastwhisper.jvm.classloading.initiative;
/**
* @author lastwhisper
*/
public class SubClass extends SuperClass {
public static int subvalue = 456;
static {
System.out.println("SubClass static code init!");
}
public SubClass() {
System.out.println("SubClass constructor init! ");
}
public static int getSubvalue() {
return subvalue;
}
public static void setSubvalue(int subvalue) {
SubClass.subvalue = subvalue;
}
}
2.1 情景一
遇到new嫁盲、getstatic篓叶、putstatic或invokestatic這4條字節(jié)碼指令時,如果類沒有進行過初始化羞秤,則需要先觸發(fā)其初始化缸托。
package cn.lastwhisper.jvm.classloading.initiative;
/**
* 主動引用觸發(fā)初始化、演示一
* @author lastwhisper
*/
public class Initialization1 {
// 遇到new瘾蛋、getstatic俐镐、putstatic或invokestatic這4條字節(jié)碼指令時,
// 如果類沒有進行過初始化瘦黑,則需要先觸發(fā)其初始化京革。
public static void main(String[] args) {
// 1.new字節(jié)碼指令
//SubClass subClass = new SubClass();
// 2.getstatic字節(jié)碼指令
// 被final修飾、已在編譯期把結果放入常量池的靜態(tài)字段除外
//int subvalue = SubClass.subvalue;
// 3.setstatic字節(jié)碼指令
// 被final修飾幸斥、已在編譯期把結果放入常量池的靜態(tài)字段除外
//SubClass.subvalue = 789;
// 4.invokestatic字節(jié)碼指令
//SubClass.getSubvalue();
}
}
2.2 情景二
使用java.lang.reflect包的方法對類進行反射調用的時候匹摇,如果類沒有進行過初始化,則需要先觸發(fā)其初始化甲葬。
package cn.lastwhisper.jvm.classloading.initiative;
import java.lang.reflect.Constructor;
/**
* 主動引用觸發(fā)初始化廊勃、演示二
* @author lastwhisper
*/
public class Initialization2 {
public static void main(String[] args) {
// 使用java.lang.reflect包的方法對類進行反射調用的時候,
// 如果類沒有進行過初始化经窖,則需要先觸發(fā)其初始化坡垫。
try {
// 使用Class.forName();也行,不要使用對象.class画侣。
Class<SubClass> clazz = SubClass.class;
Constructor<SubClass> constructor = clazz.getConstructor();
constructor.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 情景三
當初始化一個類的時候冰悠,如果發(fā)現(xiàn)其父類還沒有進行過初始化,則需要先觸發(fā)其父類的初始化配乱。
package cn.lastwhisper.jvm.classloading.initiative;
/**
* 主動引用觸發(fā)初始化溉卓、演示三
* @author lastwhisper
*/
public class Initialization3 {
// 當初始化一個類的時候皮迟,如果發(fā)現(xiàn)其父類還沒有進行過初始化,
// 則需要先觸發(fā)其父類的初始化
public static void main(String[] args) {
// 在Initialization1的new指令和Initialization2的反射創(chuàng)建對象都有體現(xiàn)
SubClass subClass = new SubClass();
//初始化順序
//SuperClass static code init! 首先初始化父類靜態(tài)代碼塊
//SubClass static code init! 其次初始化自己的靜態(tài)代碼塊
//SuperClass constructor init! 其次初始化父類的構造器
//SubClass constructor init! 其次初始化自己的構造器
}
}
2.4 情景四
當虛擬機啟動時桑寨,用戶需要指定一個要執(zhí)行的主類( 包含main()方法的那個類)伏尼,虛擬機會先初始化這個主類。
package cn.lastwhisper.jvm.classloading.initiative;
/**
* 主動引用觸發(fā)初始化尉尾、演示四
* @author lastwhisper
*/
public class Initialization4 {
static {
System.out.println("Initialization4 static code init!");
}
public Initialization4() {
System.out.println("Initialization4 constructor init!");
}
public static void main(String[] args) {
// 當虛擬機啟動時爆阶,用戶需要指定一個要執(zhí)行的主類(包含main()方法的那個類),虛擬機會先初始化這個主類沙咏。
}
}
2.5 情景五
當使用JDK 1.7的動態(tài)語言支持時辨图,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getStatic、REF_putStatic芭碍、REF_invokeStatic的方法句柄徒役, 并且這個方法句柄所對應的類沒有進行過初始化, 則需要先觸發(fā)其初始化窖壕。
首先創(chuàng)建一個MethodHandleClass類
package cn.lastwhisper.jvm.classloading.initiative;
/**
* @author lastwhisper
*/
public class MethodHandleClass {
static {
System.out.println("MethodHandleClass static code init!");
}
public MethodHandleClass() {
System.out.println("MethodHandleClass constructor init!");
}
// REF_invokeStatic
public static void testREF_invokeStatic(String str) {
System.out.println(str);
}
}
package cn.lastwhisper.jvm.classloading.initiative;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
/**
* 主動引用觸發(fā)初始化忧勿、演示五
* @author lastwhisper
*/
public class Initialization5 {
public static void main(String[] args) {
MethodHandles.Lookup lookup = MethodHandles.lookup();
try {
// REF_invokeStatic
MethodHandle testREF_invokeStatic = lookup.findStatic(MethodHandleClass.class, "testREF_invokeStatic", MethodType.methodType(void.class, String.class));
testREF_invokeStatic.invoke("啥也不干,打印一段話");
// REF_getStatic
// REF_putStatic
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
3. 被動引用
被動引用不會觸發(fā)類的初始化
3.1 情景一
子類引用父類的靜態(tài)字段瞻讽,只會觸發(fā)子類的加載鸳吸、父類的初始化,不會導致子類初始化
package cn.lastwhisper.jvm.classloading.passive;
import cn.lastwhisper.jvm.classloading.initiative.SubClass;
/**
* 被動使用類字段不觸發(fā)初始化速勇、演示一
* @author lastwhisper
*/
public class NotInitialization1 {
// 子類引用父類的靜態(tài)字段晌砾,只會觸發(fā)子類的加載、父類的初始化烦磁,不會導致子類初始化
// 是否要觸發(fā)子類的加載和驗證养匈,在虛擬機規(guī)范中并未明確規(guī)定,這點取決于虛擬機的具體實現(xiàn)
// 對于Sun HotSpot虛擬機都伪,可通過-XX:+TraceClassLoading參數(shù)觀察到此操作會導致子類的加載
public static void main(String[] args) {
System.out.println(SubClass.value);
//[Loaded cn.lastwhisper.jvm.classloading.passive.SubClass from ...]
}
}
打印結果:子類并未初始化
SuperClass static code init!
123
3.2 情景二
通過數(shù)組定義來引用類呕乎,不會觸發(fā)此類的初始化
package cn.lastwhisper.jvm.classloading.passive;
import cn.lastwhisper.jvm.classloading.initiative.SuperClass;
/**
* 被動使用類字段不觸發(fā)初始化、演示二
* -XX:+TraceClassLoading
* @author lastwhisper
*/
public class NotInitialization2 {
public static void main(String[] args){
// 通過數(shù)組定義來引用類陨晶,不會觸發(fā)此類的初始化
// 會觸發(fā)L+全類名的初始化
SuperClass[] superClasses = new SuperClass[10];
}
}
3.3 情景三
常量在編譯階段會進行常量優(yōu)化猬仁,將常量存入調用類的常量池中,
本質上并沒有直接引用到定義常量的類先誉,因此不會觸發(fā)定義常量的類的初始化湿刽。
創(chuàng)建一個被static final修飾的類。
/**
* @author lastwhisper
*/
public class ConstClass {
public static final String HELLOWORLD = "hello world";
static {
System.out.println("ConstClass init!");
}
}
package cn.lastwhisper.jvm.classloading.passive;
/**
* 被動使用類字段不觸發(fā)初始化褐耳、演示三
* @author lastwhisper
*/
public class NotInitialization3 {
public static void main(String[] args) {
// 常量在編譯階段會進行常量優(yōu)化诈闺,將常量存入**調用類**的常量池中,
// 本質上并沒有直接引用到定義常量的類铃芦,因此不會觸發(fā)定義常量的類的初始化雅镊。
System.out.println(ConstClass.HELLOWORLD);
// hello world
}
}
打印結果:ConstClass類并未初始化把曼。
hello world
參考
《深入理解Java虛擬機》