深入理解JVM(③)虛擬機(jī)的類加載時(shí)機(jī)
前言
Java虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對(duì)數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接使用的Java類型,這個(gè)過(guò)程被稱為虛擬機(jī)的類加載機(jī)制。
類加載的時(shí)機(jī)
一個(gè)類型從被加載到虛擬機(jī)內(nèi)存中開(kāi)始,到卸載除內(nèi)存為止,它的生命周期將會(huì)經(jīng)歷加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和 卸載(Unloading)、七個(gè)階段,其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱為連接(Linking)。
類的生命周期如下圖:

其實(shí)加載、驗(yàn)證、準(zhǔn)備、初始化和卸載這五個(gè)階段的順序是確定的,類型的加載過(guò)程必須按照這種順序按部就班地開(kāi)始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開(kāi)始,這是為了支持Java語(yǔ)音的運(yùn)行時(shí)綁定特性(也稱為動(dòng)態(tài)綁定或晚期綁定)。
在什么情況下需要開(kāi)始類加載過(guò)程的第一個(gè)階段“加載”,《Java虛擬機(jī)規(guī)則》中并沒(méi)有進(jìn)行強(qiáng)制約束,但是對(duì)于初始化階段《Java虛擬機(jī)規(guī)范》則是嚴(yán)格規(guī)定了有且只有以下六種情況必須立即對(duì)類進(jìn)行“初始化”。
- 遇到
new、getstatic、putstatic或invokestatic這四條字節(jié)碼指令時(shí),如果類型沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化階段。
涉及到這四條指令的典型場(chǎng)景有:
- 使用new關(guān)鍵字實(shí)例化對(duì)的時(shí)候。
- 讀取或設(shè)置一個(gè)類型的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候。
- 調(diào)用一個(gè)類型的靜態(tài)方法的時(shí)候。
- 使用
java.lang.reflect包的方法對(duì)類型進(jìn)行反射調(diào)用的時(shí)候,如果類型沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。 - 當(dāng)初始化類型的時(shí)候,如果發(fā)現(xiàn)其父類還沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其父類的初始化。
- 當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)主類。
- 當(dāng)使用JDK7新加入的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)
java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果為REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四種類型的方法句柄,并且這個(gè)方法句柄對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化。 - 當(dāng)一個(gè)接口中定義了JDK8新加入的默認(rèn)方法(被
default關(guān)鍵字修飾的接口方法)時(shí),如果這個(gè)接口的實(shí)現(xiàn)類發(fā)生了初始化,那該接口要在其之前被初始化。
除了以上的這個(gè)六種場(chǎng)景外,所有引用類型的方式都不會(huì)觸發(fā)初始化,稱為被動(dòng)引用。
下面來(lái)看一下哪些是被動(dòng)引用:
例子??1:
父類
package com.jimoer.classloading;
/**
* @author jimoer
* @date Create in 2020/06/24 16:08
* @description 通過(guò)子類引用父類的靜態(tài)字段,不會(huì)導(dǎo)致子類初始化。
*/
public class FatherClass {
static {
System.out.println("FatherClass init!!!!!");
}
public static int value = 666;
}
子類
package com.jimoer.classloading;
public class SonClass extends FatherClass{
static {
System.out.println("SonClass init!!!");
}
}
測(cè)試類
@Test
public void testInitClass(){
System.out.println(SonClass.value);
}
運(yùn)行結(jié)果:
FatherClass init!!!!!
666
通過(guò)運(yùn)行結(jié)果我們看到,只輸出了“FatherClass init!!!!!”,并沒(méi)有輸出“SubClass init!!!”,這是因?yàn)閷?duì)于使用靜態(tài)字段,只有直接定義這個(gè)字段的類才會(huì)被初始化,因此通過(guò)子類來(lái)引用父類中定義的靜態(tài)字段,并不會(huì)初始化子類。
例子??2:
/**
* 通過(guò)數(shù)組定義來(lái)引用類,不會(huì)觸發(fā)此類的初始化
*/
@Test
public void testInitClass2(){
FatherClass[] fathers = new FatherClass[5];
}
運(yùn)行結(jié)果:未打印任何信息。
通過(guò)運(yùn)行結(jié)果我們發(fā)現(xiàn),并沒(méi)有打印出 FatherClass init!!!!! ,這說(shuō)明并沒(méi)有觸發(fā)Father類的初始化階段。但是這段代碼里面觸發(fā)了另一個(gè)名為“[Lcom.jimoer.classloading.FatherClass”的類的初始化階段,它是一個(gè)由虛擬機(jī)自動(dòng)生成的、直接繼承與java.lang.Object的子類,創(chuàng)建動(dòng)作由字節(jié)碼newarray觸發(fā)。這個(gè)類代表了一個(gè)元素類型為FatherClass的一維數(shù)組,數(shù)組中應(yīng)用的屬性和方法(length屬性和clone()方法)都實(shí)現(xiàn)在這個(gè)類里。
例子??3:
/**
* @author jimoer
* 常量在編譯階段會(huì)存入調(diào)用類的常量池中,
* 本質(zhì)上沒(méi)有直接引用到定義常量的類,
* 因此不會(huì)觸發(fā)定義常量的類的初始化。
*/
public class ConstantClass {
static {
System.out.println("ConstantClass init !!!");
}
public static final String CLASS_LOAD = "class load test !!!";
}
使用
/**
* 使用常量
*/
@Test
public void testInitClass3(){
System.out.println(ConstantClass.CLASS_LOAD);
}
運(yùn)行結(jié)果:
class load test !!!
通過(guò)運(yùn)行結(jié)果,我們看到當(dāng)在使用一個(gè)類的常量時(shí),并不會(huì)初始化定義了常量的類。這是因?yàn)殡m然在Java源碼中確實(shí)引用了ConstatClass的類的常量CLASS_LOAD,但其實(shí)在編譯階段通過(guò)常量傳播優(yōu)化,已經(jīng)將此常量的值“class load test !!!”直接存儲(chǔ)在使用常量的類中的常量池中了,所以在使用ConstantClass.CLASS_LOAD時(shí)候,實(shí)際上都被轉(zhuǎn)化為在使用類自身的常量池的引用了。
接口也是有初始化過(guò)程的,上面的代碼都是用靜態(tài)語(yǔ)句塊“static {}”來(lái)輸出初始化信息的,而接口中不能使用static{}語(yǔ)句塊,但編譯器仍然會(huì)為接口生成“
還有一點(diǎn)接口與類不同,當(dāng)一個(gè)類在初始化時(shí),要求其父類全部都已經(jīng)初始化過(guò)了,但是在一個(gè)接口初始化時(shí),并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的時(shí)候(例如引用接口中的常量)才會(huì)初始化。
作者:紀(jì)莫
歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處。
限于本人水平,如果文章和代碼有表述不當(dāng)之處,還請(qǐng)不吝賜教。
歡迎掃描二維碼關(guān)注公眾號(hào):Jimoer
文章會(huì)同步到公眾號(hào)上面,大家一起成長(zhǎng),共同提升技術(shù)能力。
聲援博主:如果您覺(jué)得文章對(duì)您有幫助,可以點(diǎn)擊文章右下角【推薦】一下。
您的鼓勵(lì)是博主的最大動(dòng)力!


浙公網(wǎng)安備 33010602011771號(hào)