JVM的類(lèi)加載的過(guò)程

類(lèi)加載過(guò)程包括加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization),其中驗(yàn)證、準(zhǔn)備、解析三個(gè)部分統(tǒng)稱(chēng)為連接(Linking)

1、加載

“加載(Loading)”階段是整個(gè)“類(lèi)加載(Class Loading)”過(guò)程中的一個(gè)階段。在加載階段,Java虛擬機(jī)需要完成以下三件事情:

1)通過(guò)一個(gè)類(lèi)的全限定名來(lái)獲取定義此類(lèi)的二進(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è)類(lèi)的java.lang.Class對(duì)象,作為方法區(qū)這個(gè)類(lèi)的各種數(shù)據(jù)的訪問(wèn)入口。

加載階段與連接階段的部分動(dòng)作(如一部分字節(jié)碼文件格式驗(yàn)證動(dòng)作)是交叉進(jìn)行的,加載階段尚未完成,連接階段可能已經(jīng)開(kāi)始,但這些夾在加載階段之中進(jìn)行的動(dòng)作,仍然屬于連接階段的一部分,這兩個(gè)階段的開(kāi)始時(shí)間仍然保持著固定的先后順序。

2、驗(yàn)證

驗(yàn)證是連接階段的第一步,這一階段的目的是確保Class文件的字節(jié)流中包含的信息被當(dāng)作代碼運(yùn)行后不會(huì)危害虛擬機(jī)自身的安全。驗(yàn)證階段大致上會(huì)完成下面四個(gè)階段的檢驗(yàn)動(dòng)作:文件格式驗(yàn)證、元數(shù)據(jù)驗(yàn)證、字節(jié)碼驗(yàn)證和符號(hào)引用驗(yàn)證。

2.1、文件格式驗(yàn)證

第一階段要驗(yàn)證字節(jié)流是否符合Class文件格式的規(guī)范,并且能被當(dāng)前版本的虛擬機(jī)處理。這一階段可能包括下面這些驗(yàn)證點(diǎn):

1)是否以魔數(shù)0xCAFEBABE開(kāi)頭。

2)主、次版本號(hào)是否在當(dāng)前Java虛擬機(jī)接受范圍之內(nèi)。

3)常量池的常量中是否有不被支持的常量類(lèi)型(檢查常量tag標(biāo)志)。

4)指向常量的各種索引值中是否有指向不存在的常量或不符合類(lèi)型的常量。

5)CONSTANT_Utf8_info型的常量中是否有不符合UTF-8編碼的數(shù)據(jù)。

6)Class文件中各個(gè)部分及文件本身是否有被刪除的或附加的其他信息。

7)......

該驗(yàn)證階段的主要目的是保證輸入的字節(jié)流能正確地解析并存儲(chǔ)于方法區(qū)之內(nèi),格式上符合描述一個(gè)Java類(lèi)型信息的要求。這階段的驗(yàn)證是基于二進(jìn)制字節(jié)流進(jìn)行的,只有通過(guò)了這個(gè)階段的驗(yàn)證之后,這段字節(jié)流才被允許進(jìn)入Java虛擬機(jī)內(nèi)存的方法區(qū)中進(jìn)行存儲(chǔ),所以后面的三個(gè)驗(yàn)證階段全部是基于方法區(qū)的存儲(chǔ)結(jié)構(gòu)上進(jìn)行的,不會(huì)再直接讀取、操作字節(jié)流了。

2.2、元數(shù)據(jù)驗(yàn)證

第二階段是對(duì)字節(jié)碼描述的信息進(jìn)行語(yǔ)義分析,以保證其描述的信息符合《Java語(yǔ)言規(guī)范》的要求,這個(gè)階段可能包括的驗(yàn)證點(diǎn)如下:

1)這個(gè)類(lèi)是否有父類(lèi)(除了java.lang.Object之外,所有的類(lèi)都應(yīng)當(dāng)有父類(lèi))。

2)這個(gè)類(lèi)的父類(lèi)是否繼承了不允許被繼承的類(lèi)(被final修飾的類(lèi))。

3)如果這個(gè)類(lèi)不是抽象類(lèi),是否實(shí)現(xiàn)了其父類(lèi)或接口之中要求實(shí)現(xiàn)的所有方法。

4)類(lèi)中的字段、方法是否與父類(lèi)產(chǎn)生矛盾(例如覆蓋了父類(lèi)的final字段,或者出現(xiàn)不符合規(guī)則的方法重載,例如方法參數(shù)都一致,但返回值類(lèi)型卻不同等)。

5)......

2.3、字節(jié)碼驗(yàn)證

第三階段是整個(gè)驗(yàn)證過(guò)程中最復(fù)雜的一個(gè)階段,主要目的是通過(guò)數(shù)據(jù)流分析和控制流分析,確定程序語(yǔ)義是合法的、符合邏輯的。在第二階段對(duì)元數(shù)據(jù)信息中的數(shù)據(jù)類(lèi)型校驗(yàn)完畢以后,這階段就要對(duì)類(lèi)的方法體(Class文件中的Code屬性)進(jìn)行校驗(yàn)分析,保證被校驗(yàn)類(lèi)的方法在運(yùn)行時(shí)不會(huì)做出危害虛擬機(jī)安全的行為,例如:

1)保證任意時(shí)刻操作數(shù)棧的數(shù)據(jù)類(lèi)型與指令代碼序列都能配合工作,例如不會(huì)出現(xiàn)類(lèi)似于“在操作棧放置了一個(gè)int類(lèi)型的數(shù)據(jù),使用時(shí)卻按long類(lèi)型來(lái)加載入本地變量表中”這樣的情況。

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

3)保證方法體中的類(lèi)型轉(zhuǎn)換總是有效的,例如可以把一個(gè)子類(lèi)對(duì)象賦值給父類(lèi)數(shù)據(jù)類(lèi)型,這是安全的,但是把父類(lèi)對(duì)象賦值給子類(lèi)數(shù)據(jù)類(lèi)型,甚至把對(duì)象賦值給與它毫無(wú)繼承關(guān)系、完全不相干的一個(gè)數(shù)據(jù)類(lèi)型,則是危險(xiǎn)和不合法的。

4)......

2.4、符號(hào)引用驗(yàn)證

最后一個(gè)階段的校驗(yàn)行為發(fā)生在虛擬機(jī)將符號(hào)引用轉(zhuǎn)化為直接引用的時(shí)候,這個(gè)轉(zhuǎn)化動(dòng)作將在連接的第三階段——解析階段中發(fā)生。符號(hào)引用驗(yàn)證可以看作是對(duì)類(lèi)自身以外(常量池中的各種符號(hào)引用)的各類(lèi)信息進(jìn)行匹配性校驗(yàn),通俗來(lái)說(shuō)就是,該類(lèi)是否缺少或者被禁止訪問(wèn)它以來(lái)的某些外部類(lèi)、方法、字段等資源。本階段通常需要校驗(yàn)下列內(nèi)容:

1)符號(hào)引用中通過(guò)字符串描述的全限定名是否能找到對(duì)應(yīng)的類(lèi)。

2)在指定類(lèi)中是否存在符合方法的字段描述符及簡(jiǎn)單名稱(chēng)所描述的方法和字段。

3)符號(hào)引用中的類(lèi)、字段、方法的可訪問(wèn)性(private、protected、public、<package>)是否可被當(dāng)前類(lèi)訪問(wèn)。

4)......

符號(hào)引用驗(yàn)證的主要目的是確保解析行為能正常執(zhí)行,如果無(wú)法通過(guò)符號(hào)引用驗(yàn)證,Java虛擬機(jī) 將會(huì)拋出一個(gè)java.lang.IncompatibleClassChangeError的子類(lèi)異常,典型的如: java.lang.IllegalAccessError、java.lang.NoSuchFieldError、java.lang.NoSuchMethodError等。

3、準(zhǔn)備

準(zhǔn)備階段是正式為類(lèi)中定義的變量(即靜態(tài)變量,被static修飾的變量)分配內(nèi)存并設(shè)置類(lèi)變量初始值的階段。注意這里有兩個(gè)容易產(chǎn)生混淆的概念:一是這時(shí)候進(jìn)行內(nèi)存分配的僅包括類(lèi)變量、而不包括實(shí)例變量,實(shí)例變量將會(huì)在對(duì)象實(shí)例化時(shí)隨著對(duì)象一起分配在Java堆中;二是這里所說(shuō)的初始值“通常情況”下時(shí)數(shù)據(jù)類(lèi)型的零值。下表是Java中所有基本類(lèi)型的零值:

4、解析

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

1)符號(hào)引用(Symbolic References):符號(hào)引用以一組符號(hào)來(lái)描述所引用的目標(biāo),符號(hào)可以是任何形式的字面量,只要使用時(shí)能無(wú)歧義地定位到目標(biāo)即可。符號(hào)引用與虛擬機(jī)實(shí)現(xiàn)地內(nèi)存布局無(wú)關(guān),引用的目標(biāo)并不一定是已經(jīng)加載到虛擬機(jī)內(nèi)存當(dāng)中的內(nèi)容。

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

解析階段什么時(shí)候執(zhí)行可以根據(jù)虛擬機(jī)的需要自行判定,只需要在執(zhí)行ane-warray、 checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invoke-special、 invokestatic、invokevirtual、ldc、ldc_w、ldc2_w、multianewarray、new、putfield和putstatic這17個(gè)用于操作符號(hào)引用的字節(jié)碼指令之前,先對(duì)它們所使用的符號(hào)引用進(jìn)行解析。

5、初始化

類(lèi)的初始化階段是類(lèi)加載過(guò)程的最后一個(gè)步驟,之前介紹的幾個(gè)類(lèi)加載的動(dòng)作里,除了在加載階段用戶(hù)應(yīng)用程序可以通過(guò)自定義類(lèi)加載器的方式局部參與外,其余動(dòng)作都完全由Java虛擬機(jī)來(lái)主導(dǎo)控制。直到初始化階段,Java虛擬機(jī)才真正開(kāi)始執(zhí)行類(lèi)中編寫(xiě)的Java程序代碼,將主導(dǎo)權(quán)移交給應(yīng)用程序。

進(jìn)行準(zhǔn)備階段時(shí),變量已經(jīng)賦過(guò)一次系統(tǒng)要求的初始零值,而在初始化階段,則會(huì)根據(jù)程序員通過(guò)程序編碼制定的主觀計(jì)劃去初始化類(lèi)變量和其他資源。初始化階段就是執(zhí)行類(lèi)構(gòu)造器<clinit>()方法的過(guò)程。

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

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

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