初始化的五種情況加載階段驗證階段準(zhǔn)備階段解析階段符號引用和直接引用類或接口的解析字段解析類方法解析接口方法解析
類加載機(jī)制
類的生命周期
- 加載 2. 驗證 3. 準(zhǔn)備 4. 解析 5. 初始化 6. 使用 7. 卸載
初始化的五種情況
有且僅有五種情況會立即對類進(jìn)行初始化,此五種情況稱為對一個類的主動引用,其余均為被動引用
遇到new、getstatic、putstatic、invokestatic這四條字節(jié)碼指令時
使用java.lang.reflect包方法對類進(jìn)行反射調(diào)用的時候,如果類沒有進(jìn)行過初始化,則需要將其初始化
當(dāng)初始化一個類的時候,如果父類沒有初始化,則需要先觸發(fā)父類的初始化(接口在初始化時并不會要求其父類全部已經(jīng)完成初始化,只有真正使用到父接口的時候(如引用接口中定義的常量)才會初始化)
當(dāng)虛擬機(jī)啟動時,用戶需要指定一個要執(zhí)行的主類(包含main()方法的類),虛擬機(jī)會先初始化主類
當(dāng)使用jdk1.7的動態(tài)語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結(jié)果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄
加載階段
”加載“是”類加載過程的一個階段
-
加載階段虛擬機(jī)需要完成以下三個步驟
通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流(來源:zip包、網(wǎng)絡(luò)、運行時計算生成、數(shù)據(jù)庫讀取等)
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口
數(shù)組類本身不通過加載器加載,它是由java虛擬機(jī)直接創(chuàng)建的
1.如果數(shù)組類的組件類型是引用類,那就可以采用,那就會遞歸采用上述加載過程,去加載這個組件類型,數(shù)組類將在加載該組件類型的類加載器上被標(biāo)識
?
2.如果數(shù)組類的組件類型為非引用類型(如int[],byte[]等),Java虛擬機(jī)會把數(shù)組類標(biāo)記為與引導(dǎo)類加載器關(guān)聯(lián)
?
3.數(shù)組類的可見性與它組件類型的可見性一致,如果組件類型不是引用類型,那其可見性就將默認(rèn)為public</pre>
驗證階段
雖然Java語言是相對比較安全的語言,但是由于加載階段的字節(jié)流來源多樣,在字節(jié)碼語言層面上,會有很多Java禁止的不安全操作(如訪問數(shù)組邊界外的數(shù)據(jù),將一個對象轉(zhuǎn)型為它從未實現(xiàn)過的類等),因此驗證字節(jié)流是虛擬機(jī)對自身的一個保護(hù)機(jī)制,免受惡意代碼攻擊。
-
驗證的四個階段
文件格式驗證
元數(shù)據(jù)驗證
字節(jié)碼驗證
符號引用驗證
文件格式驗證階段
主要驗證字節(jié)流是否符合Class文件格式的規(guī)范和是否能被當(dāng)前虛擬機(jī)處理
元數(shù)據(jù)驗證階段
對字節(jié)碼描述的信息進(jìn)行語義分析,主要是對數(shù)據(jù)類型做校驗,以保證其描述的信息符合Java語言規(guī)范的要求。
如:
- 這個類是否有父類(除了java.lang.Object)都應(yīng)該有父類 2. 非抽象類是否已全部實現(xiàn)父類中要求實現(xiàn)的方法 3. 是否繼承了不被允許繼承的父類(被final修飾的類)
...
字節(jié)碼驗證
第三階段的校驗最為復(fù)雜,主要目的是通過數(shù)據(jù)流和控制流的分析對類的方法體進(jìn)行校驗分析。
保證不會出現(xiàn)跳轉(zhuǎn)指令跳轉(zhuǎn)到方法體以外的字節(jié)碼指令上
保證方法體中的類型轉(zhuǎn)換有效
保證不會出現(xiàn)類似于一long類型來載入int類型數(shù)據(jù)
...
符號引用驗證
發(fā)生在虛擬機(jī)將符號引用轉(zhuǎn)化為直接引用的時候,在解析階段中發(fā)生。符號引用可以理解為對類自身以外的信息進(jìn)行匹配性校驗,目的是確保解析動作能夠正常執(zhí)行
-
校驗內(nèi)容
符號引用通過字符串描述的全限定名能否找到對應(yīng)的類
符號引用中的類、字段、方法的訪問性是否可被當(dāng)前類訪問
準(zhǔn)備階段
準(zhǔn)備階段是正式為類變量(被static修飾,不包含實例變量)分配內(nèi)存和設(shè)置類變量初始值的階段,這些變量所使用的內(nèi)存都將在方法區(qū)分配。
初始值”通常情況“下是數(shù)據(jù)類型的零值,”特殊情況“是當(dāng)變量被定義為ConstantValue(被final修飾),則會根據(jù)其設(shè)置將value賦值
例:public static final int value = 123; 準(zhǔn)備階段value被賦值為123
解析階段
解析階段將常量池內(nèi)的符號引用替換為直接引用的過程。
解析階段發(fā)生的具體時間并未明確規(guī)定,只要求了在執(zhí)行anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、newputfield和putstatic這16個用于操作符號引用的字節(jié)碼指令之前,先對他們所使用的符號引用進(jìn)行解析。
符號引用和直接引用
-
符號引用:
以一組符號來描述所引用的目標(biāo),可以是任何形式的字面量(字面量形式已在Class格式規(guī)范中定義,因此具有一致性,可被各種虛擬機(jī)接受),只要能無歧義地定位到目標(biāo)即可。
-
直接引用:
是可以直接指向目標(biāo)的指針、相對偏移量、或是一個能間接定位到目標(biāo)的句柄。如果有了直接引用,那引用的目標(biāo)必定已經(jīng)在內(nèi)存中存在。
符號引用的解析指令
invokedynamic指令 其它指令 動態(tài)(必須等到程序運行到這條指令的時候才執(zhí)行) 靜態(tài)(可以在剛完成加載階段,還沒開始執(zhí)行代碼階段解析) 該指令的目的用于動態(tài)語言支持,遇到已被invokedynamic指令解析過得符號引用(“動態(tài)調(diào)用點限定符”),不意味著解析結(jié)果能對其它invokedynamic指令生效。 可以對第一次解析結(jié)果進(jìn)行緩存(在常量池記錄直接引用,并把敞亮表位已解析狀態(tài)),從而避免重復(fù)解析。需要保證同一實體內(nèi),對同一符號引用,解析成功/異常是一致的。
類或接口的解析

字段解析
字段解析會首先對字段表內(nèi)的class_index項中索引的CONSTANT_Class_info符號引用進(jìn)行解析(即對字段所屬的類),對所屬類做以下后續(xù)字段的搜索

如果查找過程成功返回了引用,則對這個字段進(jìn)行權(quán)限驗證。如果發(fā)現(xiàn)對這個字段不具有訪問權(quán)限,則拋出java.lang.IlleagalAccessError。
類方法解析
類的解析的第一階段與字段解析一樣。

接口方法解析
大致與類方法相似,由于借口不存在父類只存在父接口,只缺少對父類遞歸搜索處理。