什么是類加載機制
JVM把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存迫悠,并對數(shù)據(jù)進行校驗、轉(zhuǎn)換解析和初始化,最終形成可以被JVM直接使用的Java類型贾惦,這就是JVM的類加載機制。
類的生命周期
類從被加載到內(nèi)存中午乓,到被卸載出內(nèi)存,一共分為以下幾步:
- 加載(Loading)
- 驗證(Verification)
- 準備(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸載(Unloading)
類加載的全過程,包括其中的加載惧磺、驗證、準備捻撑、解析磨隘、初始化
幾個階段。
加載
加載是類加載的第一階段顾患,在這一步中JVM規(guī)范要求完成了以下三件事:
通過一個類的全限定名來獲取定義這個類的二進制字節(jié)流番捂。
將這個字節(jié)流多代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)。
在內(nèi)存中生成一個代表這個類的java.lang.Class對象描验。
以上要求其實并不具體白嘁,JVM的具體實現(xiàn)和應(yīng)用都是比較靈活的。比如:獲取這個類的二進制字節(jié)流膘流,并沒有說從哪獲取絮缅,怎么獲取,于是就有了從壓縮包中讀群艄伞(jar耕魄、war、ear)彭谁、從網(wǎng)絡(luò)中獲任(Applet)、運行時計算生成(動態(tài)代理)缠局。對于不是數(shù)組的類的加載则奥,我們可以定義自己的類加載器去控制字節(jié)流的獲取方式。但是狭园,對于數(shù)組類就不一樣了读处,因為數(shù)組類本身不是通過類加載器創(chuàng)建的,而是JVM直接創(chuàng)建的唱矛。
驗證
這一階段是為了保證Class文件的字節(jié)流中包含的信息符合當前JVM的要求罚舱,并且不危害JVM自身的安全。大致分為以下四個階段:
文件格式驗證
驗證字節(jié)流是否符合Class文件格式的規(guī)范绎谦,能不能被當前JVM處理管闷。驗證點比較多,比如:是否以魔數(shù)0xCAFEBABE開頭窃肠、主次版本號是否在當前JVM的處理范圍內(nèi)包个、常量池的常量是否有不被支持的常量類型、CONSTANT_Utf8_info類型的常量中是否有不符合UTF8編碼的數(shù)據(jù)等等冤留。這個階段是基于二進制字節(jié)流進行驗證的赃蛛,只有這個階段驗證通過了恃锉,字節(jié)流才能進入內(nèi)存的方法區(qū)儲存。
元數(shù)據(jù)驗證
這個階段主要是對類的元數(shù)據(jù)信息進行語義分析和校驗呕臂,保證不存在不符合Java語言規(guī)范的元數(shù)據(jù)信息破托。比如:除了java.lang.Object以外的類是否有父類、是否繼承了一個不允許被繼承的類歧蒋、非抽象類是否實現(xiàn)了其父類或接口中要求實現(xiàn)的所有方法土砂、是否覆蓋了父類的final字段等等。
字節(jié)碼校驗
這個階段通過數(shù)據(jù)流和控制流分析谜洽,確保程序語義是合法的萝映、符合邏輯的。比如:放置和使用操作棧時數(shù)據(jù)類型保證一致阐虚、保證跳轉(zhuǎn)指令不會跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上序臂、保證方法體中的類型轉(zhuǎn)換是有效的等等。
符號引用校驗
這個階段是對類自身以外(常量池中的各種符號引用)的信息進行匹配性校驗实束,它發(fā)生在解析步驟中奥秆,確保解析能正常執(zhí)行,比如:符號引用中通過字符串描述的全限定名是否能找到對應(yīng)的類咸灿、符號引用中的類字段方法的訪問性是否可以訪問當前類等等构订。
準備
在這個階段里,為靜態(tài)變量分配內(nèi)存并設(shè)置靜態(tài)變量初始值避矢。這里說的初始值通常情況下悼瘾,不是代碼中寫的初始值,而是數(shù)據(jù)類型的零值审胸。代碼中寫的初始值亥宿,是在初始化階段賦值的。如果是靜態(tài)常量(被final修飾)砂沛,這個階段就會被直接賦值為代碼中寫的初始值烫扼。
解析
在這個階段里,JVM把常量池內(nèi)的符號引用替換為直接引用尺上。符號引用以一組符號來描述所引用的目標材蛛,符號可以是任何形式的字面量圆到,只要使用時能無歧義地定位到目標即可怎抛,它和JVM實現(xiàn)的內(nèi)存布局無關(guān)。直接引用可以是直接指向目標的指針芽淡、相對偏移量或是一個能間接定位到目標的句柄马绝,它是和JVM實現(xiàn)的內(nèi)存布局相關(guān)的。如果有了直接引用挣菲,那么引用的目標肯定在內(nèi)存中存在富稻。
解析主要針對類或接口掷邦、字段、類方法椭赋、接口方法抚岗、方法類型、方法句柄和調(diào)用點限定符的符號引用進行哪怔,分別對應(yīng)常量池的CONSTANT_Class_info宣蔚、CONSTANT_Fieldref_info、CONSTANT_Methodref_info认境、CONSTANT_InterfaceMethodref_info胚委、CONSTANT_MethodType_info、CONSTANT_MethodHandle_info和CONSTANT_InvokeDynamic_info叉信。
初始化
初始化階段才真正開始執(zhí)行類中定義的字節(jié)碼亩冬,也是執(zhí)行類構(gòu)造器()方法的過程。()方法是由編譯器自動收集類中的所有靜態(tài)變量的賦值動作和靜態(tài)語句塊中的語句合并產(chǎn)生的硼身,編譯器收集的順序是用語句在源文件中出現(xiàn)的順序所決定的硅急,靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量,定義在它之后的變量鸠姨,靜態(tài)語句塊可以賦值铜秆,但是不能訪問。
JVM會保證在子類的()方法執(zhí)行之前讶迁,父類的()方法已經(jīng)執(zhí)行完畢连茧,也就是說父類中定義的靜態(tài)語句塊要優(yōu)先于子類的變量賦值操作。如果類沒有靜態(tài)語句塊巍糯,也沒有對靜態(tài)變量賦值啸驯,編譯器就不會為這個類生成()方法。接口的()方法不需要先執(zhí)行父接口的()方法祟峦,只有當父接口中定義的變量使用時罚斗,父接口才會被初始化。
JVM會保證一個類的()方法在多線程環(huán)境中被正確地加鎖宅楞、同步针姿。如果一個線程在執(zhí)行這個類的()方法,其他線程都需要阻塞等待厌衙,當()方法執(zhí)行完后距淫,其他線程也不會再次進入()方法。同一個類加載器下婶希,一個類只會被初始化一次榕暇。
結(jié)語
這次我們了解了類加載過程的幾個階段,分別是加載、驗證彤枢、準備狰晚、解析和初始化。加載是把二進制字節(jié)碼載入內(nèi)存缴啡,驗證是校驗字節(jié)流中包含的信息是否符合當要求壁晒,準備是為靜態(tài)變量分配內(nèi)存并設(shè)置靜態(tài)變量初始值,解析是把常量池內(nèi)的符號引用替換為直接引用业栅,初始化是執(zhí)行所有靜態(tài)變量的賦值動作和靜態(tài)語句塊中的語句讨衣。