類加載過程


類加載的時(shí)機(jī):

1.遇到new、getstatic、putstatic或invokestatic這4條字節(jié)碼指令時(shí),如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。生成這4條指令的最常見的Java代碼場景是:使用new關(guān)鍵字實(shí)例化對象的時(shí)候、讀取或設(shè)置一個(gè)類的靜態(tài)字段(被final修飾、已在編譯期把結(jié)果放入常量池的靜態(tài)字段除外)的時(shí)候,以及調(diào)用一個(gè)類的靜態(tài)方法的時(shí)候。

2.使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用的時(shí)候,如果類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

3.當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行過初始化,則需要先觸發(fā)其父類的初始化。

4.當(dāng)虛擬機(jī)啟動(dòng)時(shí),用戶需要指定一個(gè)要執(zhí)行的主類(包含main()方法的那個(gè)類),虛擬機(jī)會(huì)先初始化這個(gè)類。

5.當(dāng)使用JDK1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后的解析結(jié)果REF_getStatic、REF_invokeStatic的方法句柄,并且這個(gè)方法句柄所對應(yīng)的類沒有進(jìn)行過初始化,則需要先觸發(fā)其初始化。

類加載的過程:

1.加載:在加載階段,虛擬機(jī)需要完成以下三件事情:

1)通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流。

2)將這個(gè)字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)。

3)在內(nèi)存中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)這個(gè)類的各種數(shù)據(jù)的訪問入口。

從上述要求來說,加載這一過程的要求相對寬松,因此虛擬機(jī)實(shí)現(xiàn)與具體應(yīng)用的靈活度都是相當(dāng)大的。例如“通過一個(gè)類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流”這條,它沒有指明二進(jìn)制字節(jié)流要從一個(gè)Class文件中獲取,準(zhǔn)確地說是根本沒有指明要從哪里獲取、怎樣獲取。虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)在加載階段搭建了一個(gè)相當(dāng)開放的、廣闊的“舞臺(tái)”,java發(fā)展歷程中,充滿創(chuàng)造力的開發(fā)人員則在這個(gè)“舞臺(tái)”上玩出了各種花樣,許多舉足輕重的Java技術(shù)都建立在這一基礎(chǔ)之上。

加載階段完成后,虛擬機(jī)外部的二進(jìn)制字節(jié)流就按照虛擬機(jī)所需的格式存儲(chǔ)在方法區(qū)之中,方法區(qū)中的數(shù)據(jù)存儲(chǔ)格式由虛擬機(jī)實(shí)現(xiàn)自行定義,虛擬機(jī)規(guī)范未規(guī)定此區(qū)域的具體數(shù)據(jù)結(jié)構(gòu)。然后在內(nèi)存中實(shí)例化一個(gè)java.lang.Class類的對象(并沒有明確規(guī)定是在Java堆中,對于HotSpot虛擬機(jī)而言,Class對象比較特殊,它雖然是對象,但是存放在方法區(qū)里面),這個(gè)對象將作為程序訪問方法區(qū)中的這些類型數(shù)據(jù)的外部接口。


2.驗(yàn)證:驗(yàn)證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會(huì)危害虛擬機(jī)自身的安全。

Java語言本身是相對安全的語言,使用純粹的Java代碼無法做到諸如訪問數(shù)組邊界以外的數(shù)據(jù)、將一個(gè)對象轉(zhuǎn)型為它并為實(shí)現(xiàn)的類型,如果這樣做編譯器將拒絕執(zhí)行,但前面已經(jīng)說過,Class文件并不一定要求用Java源碼編譯而來,可以使用任何途徑產(chǎn)生,甚至包括用十六進(jìn)制編輯器直接編寫來產(chǎn)生Class文件。在字節(jié)碼層面上,上述Java代碼無法做到的事情都是可以實(shí)現(xiàn)的,至少語義上是可以表達(dá)出來的。

驗(yàn)證階段的工作量在虛擬機(jī)的類加載子系統(tǒng)中非常重要,又占了相當(dāng)大的一部分。

1.文件格式驗(yàn)證:

? a.是否以魔數(shù)0xCAFEBABE開頭;? ? ? ??

? b.主、次版本號(hào)是否在當(dāng)前虛擬機(jī)處理范圍之內(nèi);??

? c.常量池的常量中是否有不被支持的常量類型;

2.元數(shù)據(jù)驗(yàn)證:對字節(jié)碼描述的信息進(jìn)行語義分析。

? ?a.這個(gè)類是否有父類;

? ?b.這個(gè)類的父類是否繼承了不允許被繼承的類;

3.字節(jié)碼驗(yàn)證:主要目的是通過數(shù)據(jù)流和控制流分析,確定程序語義是合法的、符合邏輯的。將對類的方法體進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類的方法在運(yùn)行時(shí)不會(huì)危害虛擬機(jī)。

? ? a.保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類型與指令代碼序列都能配合工作。

? ? b.保證跳轉(zhuǎn)指令不會(huì)跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上。

4.符號(hào)引用驗(yàn)證:對類自身以外的信息進(jìn)行匹配性校驗(yàn)。


3.準(zhǔn)備:準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)中進(jìn)行分配。

? ? ?這時(shí)候進(jìn)行內(nèi)存分配的僅包括類變量(被static修飾的變量),而不包括實(shí)例變量,實(shí)例變量將會(huì)在對象實(shí)例化時(shí)隨著對象一起分配在Java堆中,其次,這里所說的初始值“通常情況”下是數(shù)據(jù)類型的零值。


4.解析:該階段是虛擬機(jī)將常量池內(nèi)的符號(hào)引用替換為直接引用的過程

符號(hào)引用:符號(hào)引用以一組符號(hào)來描述所飲用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局無關(guān),引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中。各種虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局可以各不相同,但是他們能接受的符號(hào)引用必須都是一致的,因?yàn)榉?hào)引用的字面量形式明確定義在Java虛擬機(jī)規(guī)范的Class文件格式中。

直接引用:直接引用可以是直接指向目標(biāo)的指針、相對偏移量或是一個(gè)能間接定位到目標(biāo)的句柄。直接引用是和虛擬機(jī)實(shí)現(xiàn)的內(nèi)存布局相關(guān)的,同一個(gè)符號(hào)引用在不同虛擬機(jī)實(shí)例上翻譯出來的直接引用一般不會(huì)相同。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。

解析動(dòng)作主要針對類或接口、字段、類方法、接口方法、方法句柄和調(diào)用點(diǎn)限定符。


5.初始化:前面的類加載過程中,除了在加載階段用戶應(yīng)用程序可以通過自定義類加載器參與之外,其余動(dòng)作都是由虛擬機(jī)主導(dǎo)和控制。到了初始化階段,才真正開始執(zhí)行類中定義的Java程序代碼。

? ? 在準(zhǔn)備階段,變量已經(jīng)賦過一次系統(tǒng)要求的初始值,而在初始化階段,則根據(jù)程序員通過程序定制的主觀計(jì)劃去初始化類變量和其他資源,或者可以從另外一個(gè)角度來表達(dá):初始化階段是執(zhí)行類構(gòu)造器<clinit>()方法的過程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容