定義
虛擬機(jī)把描述類的數(shù)據(jù)從Class文件中加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化陆淀,最終形成可以被虛擬機(jī)直接使用的Java類型,這就是虛擬機(jī)的類加載機(jī)制先嬉。
生命周期
類從被加載到虛擬機(jī)內(nèi)存開始轧苫,到卸載出內(nèi)存為止,整個生命周期包括:加載疫蔓、鏈接含懊、初始化、使用衅胀、卸載岔乔,其中鏈接又包括驗(yàn)證、準(zhǔn)備滚躯、解析雏门。
類的初始化
虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且僅有5種情況必須立即對類進(jìn)行初始化:
- 遇到new,getstatic,putstatic或invokestatic這4條字節(jié)碼指令時,如果類還沒有進(jìn)行初始化哀九,則需要先觸發(fā)其初始化剿配。例如:使用new關(guān)鍵字實(shí)例化對象的時候、讀取或設(shè)置一個類的靜態(tài)屬性的時候阅束、以及調(diào)用一個類的靜態(tài)方法的時候呼胚。
- 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時候,如果類還沒有進(jìn)行初始化息裸,則需要先觸發(fā)其初始化蝇更。
- 當(dāng)初始化一個類的時候沪编,如果發(fā)現(xiàn)其父類還沒有進(jìn)行初始化時,則需要先觸發(fā)父類的初始化年扩。
- 當(dāng)虛擬機(jī)啟動的時候蚁廓,用戶需要指定一個要執(zhí)行的主類,虛擬機(jī)會先初始化這個類厨幻。
- 如果一個java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄相嵌,并且這個方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化况脆。
這5種場景中的行為稱為對一個類進(jìn)行主動引用饭宾,除此之外,所有引用類的方式都不會觸發(fā)初始化格了,稱為被動引用看铆,例如:
- 通過子類引用父類的靜態(tài)字段,不會導(dǎo)致子類初始化
public class Father {
static {
System.out.println("load father");
}
public static int value = 1;
}
class Child extends Father{
static {
System.out.println("load child");
}
}
public static void main(String[] args) {
System.out.println(Child.value);
}
上述代碼不會輸出"load child"盛末;因此通過子類來引用父類中定義的靜態(tài)字段弹惦,不會觸發(fā)子類的初始化。
- 通過數(shù)組定義來引用類悄但,不會觸發(fā)此類的初始化
public static void main(String[] args) {
Father[] fathers = new Father[2];
}
上述代碼不會輸出"load father"棠隐;說明沒有觸發(fā)Father類的初始化。
- 引用常量不會觸發(fā)此類的初始化
public class Constant {
static {
System.out.println("load Constant");
}
public static final String HELLO = "hello";
}
public class TestDemo {
public static void main(String[] args) {
System.out.println(Constant.HELLO);
}
}
上述代碼不會輸出"load Constant"算墨;這是因?yàn)殡m然在Constant中定義了常量HELLO宵荒,但其實(shí)在編譯階段通過常量傳播優(yōu)化,已經(jīng)將此常量的值"hello"存儲到了TestDemo類的常量池中净嘀,以后TestDemo對常量Constant.HELLO的引用實(shí)際被轉(zhuǎn)化為TestDemo類對自身常量池的引用了。也就是說侠讯,實(shí)際上TestDemo的Class文件之中并沒有Constant類的符號引用入口挖藏,這兩個類在編譯成Class之后就不存在任何聯(lián)系了。
接口的初始化
接口也有初始化過程厢漩,上面類的初始化是通過靜態(tài)代碼塊"static{}"來輸出初始化信息的膜眠,而接口中不能使用"static{}",但編譯器任然會為接口生成"<clinit>()"類構(gòu)造器溜嗜,用于初始化接口中所定義的成員變量宵膨。接口與類真正的區(qū)別是前面5種初始化場景中的第3種:當(dāng)一個類在初始化時,要求其父類全部都已經(jīng)初始化過了炸宵,但是一個接口在初始化時辟躏,并不要求其父類全部都完成了初始化,只有在真正使用到父類接口的時候才會初始化土全。