看完這篇后,別再說你不懂JVM類加載機(jī)制了~
JVM 通過雙親委派模型進(jìn)行類的加載,即當(dāng)某個類加載器在接到加載類的請求時,首先將加載任務(wù)委托給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務(wù),就成功返回;只有父類加載器無法完成此加載任務(wù)時,才自己去加載。
類加載器
- 啟動類加載器 (Bootstrap ClassLoader) :負(fù)責(zé)加載 JAVA_HOME\lib 目錄中的,或通過 - Xbootclasspath 參數(shù)指定路徑中的,且被虛擬機(jī)認(rèn)可(按文件名識別,如 rt.jar,名字不符合的類庫即使放在 lib 目錄也不會被加載)的類。啟動類加載器無法被 Java 程序直接引用;
- 擴(kuò)展類加載器 (Extension ClassLoader) :負(fù)責(zé)加載 JAVA_HOME\jre\lib\ext 目錄中的,或通過 java.ext.dirs 系統(tǒng)變量指定路徑中的類庫;
- 應(yīng)用程序類加載器 (Application ClassLoader) :負(fù)責(zé)加載用戶路徑(classpath)上的類庫。
- 通過繼承 java.lang.ClassLoader 類實(shí)現(xiàn)自定義類加載器(主要是重寫 findClass 方法)。
小結(jié): 類加載器和字節(jié)碼是Java平臺無關(guān)性的基石,對于任意一個類,都需要由它的類加載器和這個類本身一同確立其在Java虛擬機(jī)中的唯一性。
雙親委派模型的優(yōu)點(diǎn):
- 基礎(chǔ)類的統(tǒng)一加載問題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載)。如類 java.lang.String,無論哪一個類加載器要加載這個類,最終都是委派給啟動類加載器進(jìn)行加載,所以在程序的各種類加載器環(huán)境中都是同一個類。
- 提高 java 代碼的安全性。比如說用戶自定義了一個與系統(tǒng)庫里同名的 java.lang.String 類,那么這個類就不會被加載,因?yàn)樽铐攲拥念惣虞d器會首先加載系統(tǒng)的 java.lang.String 類,而不會加載自定義的 String 類,防止了惡意代碼的注入。
- 可以避免類的重復(fù)加載,另外也避免了 Java 的核心 API 被篡改。
類加載流程
類的生命周期會經(jīng)歷以下 7 個階段:
加載階段
此階段用于查到相應(yīng)的類(通過類名進(jìn)行查找)并將此類的字節(jié)流轉(zhuǎn)換為方法區(qū)運(yùn)行時的數(shù)據(jù)結(jié)構(gòu),然后再在內(nèi)存中生成一個能代表此類的 java.lang.Class 對象,作為其他數(shù)據(jù)訪問的入口。
驗(yàn)證階段
此步驟主要是為了驗(yàn)證字節(jié)碼的安全性,如果不做安全校驗(yàn)的話可能會載入非安全或有錯誤的字節(jié)碼,從而導(dǎo)致系統(tǒng)崩潰,它是 JVM 自我保護(hù)的一項(xiàng)重要舉措。
驗(yàn)證的主要動作大概有以下幾個:
- 文件格式校驗(yàn)包括常量池中的常量類型、Class 文件的各個部分是否被刪除或被追加了其他信息等;
- 元數(shù)據(jù)校驗(yàn)包括父類正確性校驗(yàn)(檢查父類是否有被 final 修飾)、抽象類校驗(yàn)等;
- 字節(jié)碼校驗(yàn),此步驟最為關(guān)鍵和復(fù)雜,主要用于校驗(yàn)程序中的語義是否合法且符合邏輯;
- 符號引用校驗(yàn),對類自身以外比如常量池中的各種符號引用的信息進(jìn)行匹配性校驗(yàn)。
準(zhǔn)備階段
此階段是用來初始化并為類中定義的靜態(tài)變量分配內(nèi)存的,這些靜態(tài)變量會被分配到方法區(qū)上。
HotSpot 虛擬機(jī)在 JDK 1.7 之前都在方法區(qū),而 JDK 1.8 之后此變量會隨著類對象一起存放到 Java 堆中。
解析階段
此階段主要是用來解析類、接口、字段及方法的,解析時會把符號引用替換成直接引用。
所謂的符號引用是指以一組符號來描述所引用的目標(biāo),符號可以是任何形式的字面量,只要使用時能無歧義地定位到目標(biāo)即可;而直接引用是可以直接指向目標(biāo)的指針、相對偏移量或者是一個能間接定位到目標(biāo)的句柄。
符號引用和直接引用有一個重要的區(qū)別:使用符號引用時被引用的目標(biāo)不一定已經(jīng)加載到內(nèi)存中;而使用直接引用時,引用的目標(biāo)必定已經(jīng)存在虛擬機(jī)的內(nèi)存中了。
初始化
初始化階段 JVM 就正式開始執(zhí)行類中編寫的 Java 業(yè)務(wù)代碼了。到這一步驟之后,類的加載過程就算正式完成了。
總結(jié)
如上圖所示,淺綠的兩個部分表示類的生命周期,就是從類的加載到類實(shí)例的創(chuàng)建與使用,再到類對象不再被使用時可以被 GC 卸載回收。
這里要注意一點(diǎn),由 Java 虛擬機(jī)自帶的三種類加載器加載的類在虛擬機(jī)的整個生命周期中是不會被卸載的,只有用戶自定義的類加載器所加載的類才可以被卸載。