一、思維導(dǎo)圖

二、大綱
2.1 類(lèi)的加載是什么?
將類(lèi)(.class)文件中的二進(jìn)制數(shù)據(jù)讀入內(nèi)存,并放在運(yùn)行時(shí)數(shù)據(jù)庫(kù)的方法區(qū)內(nèi),在堆區(qū)創(chuàng)建java.lang.Class對(duì)象(封裝類(lèi)在方法區(qū)的數(shù)據(jù)結(jié)構(gòu))。
類(lèi)加載最終產(chǎn)品是堆區(qū)的Class對(duì)象(封裝了類(lèi)在方法區(qū)的數(shù)據(jù)結(jié)構(gòu),并向Java程序員提供了訪(fǎng)問(wèn)方法區(qū)內(nèi)的數(shù)據(jù)結(jié)構(gòu)的接口)。
2.2 類(lèi)的生命周期(重要)
2.2.1 類(lèi)的生命周期圖

注意:順序執(zhí)行的意思是開(kāi)始,而不是完成后進(jìn)行下一步
2.2.2 過(guò)程
1. 加載
-
查找并加載類(lèi)的二進(jìn)制數(shù)據(jù),在Java堆中創(chuàng)建java.lang.Class對(duì)象
JVM需要做的三件事
- 通過(guò)類(lèi)的全限定名獲取其定義的二進(jìn)制字節(jié)流
- 將此字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)
- 在Java堆中創(chuàng)建java.lang.Class對(duì)象,作為訪(fǎng)問(wèn)方法區(qū)數(shù)據(jù)的入口
-
獲取class文件的5種方式
- 本地系統(tǒng)中直接加載
- 網(wǎng)絡(luò)下載.class文件
- 從zip,jar等歸檔文件中加載.class文件
- 專(zhuān)有數(shù)據(jù)庫(kù)提取.class文件
- Java源文件動(dòng)態(tài)編譯為.class文件
注意:此階段可控性最強(qiáng),可用系統(tǒng)自帶類(lèi)加載器也可自定義類(lèi)加載器
2. 連接
2.1 驗(yàn)證
四個(gè)階段
-
文件格式
如0xCAFEBABE開(kāi)頭等 -
元數(shù)據(jù)
語(yǔ)義分析,如是否有父類(lèi)(不含Object) -
字節(jié)碼
語(yǔ)義合法 -
符號(hào)引用驗(yàn)證
保證解析能正確執(zhí)行
注意:重要但不必須,如所引用類(lèi)經(jīng)反復(fù)驗(yàn)證,可采用-Xverifynone關(guān)閉
2.2 準(zhǔn)備
為類(lèi)的靜態(tài)變量分配內(nèi)存,并將其初始化默認(rèn)值
注意:
- 僅包括類(lèi)變量(static),不含實(shí)例變量(在實(shí)例化時(shí)隨對(duì)象一塊分配在堆中)
- 此初始值指數(shù)據(jù)類(lèi)型默認(rèn)的零值,非顯示賦予的值
- 若類(lèi)字段的字段屬性表中存在ConstantValue屬性,即同時(shí)被final和static修飾,在準(zhǔn)備階段變量會(huì)被初始化為ConstantValue屬性做指定的值
2.3 解析
把類(lèi)中的符號(hào)引用(一組符號(hào)來(lái)描述)轉(zhuǎn)化為直接引用(直接指向目標(biāo)的指針、相對(duì)偏移量或一個(gè)間接定位到目標(biāo)的句柄)


-
符號(hào)引用
- 接口
- 字段
- 類(lèi)方法
- 接口方法
- 方法類(lèi)型
- 方法句柄
- 調(diào)用點(diǎn)限制符
加載順序不確定
某些情況下在初始化之后,為了支持Java語(yǔ)言的運(yùn)行時(shí)綁定(動(dòng)態(tài)綁定或晚期綁定)
3. 初始化
為類(lèi)的靜態(tài)變量賦予正確的初始值
設(shè)定的兩種方式
- 聲明類(lèi)變量是指定初始值
- 使用靜態(tài)代碼塊為類(lèi)變量指定初始值
JVM初始化步驟
- 若此類(lèi)未被加載和連接,則程序先加載并連接該類(lèi)
- 若此類(lèi)的直接父類(lèi)未初始化,則先初始化其直接父類(lèi)
- 若類(lèi)中有初始化語(yǔ)句,則系統(tǒng)依次執(zhí)行這些初始化語(yǔ)句
類(lèi)初始化時(shí)機(jī)
只有當(dāng)對(duì)類(lèi)的主動(dòng)使用的時(shí)候才會(huì)導(dǎo)致類(lèi)的初始化
- 六種主動(dòng)使用
- 創(chuàng)建類(lèi)的實(shí)例,即new
- 訪(fǎng)問(wèn)某個(gè)類(lèi)或接口的靜態(tài)變量,或?qū)υ撿o態(tài)變量賦值
- 調(diào)用類(lèi)的靜態(tài)方法
- 反射(如Class.forName("com.insigim.Test"))
- 初始化某個(gè)類(lèi)的子類(lèi),則父類(lèi)也被初始化
- Java虛擬機(jī)啟動(dòng)時(shí)被標(biāo)明為啟動(dòng)類(lèi)的類(lèi)(Java Test),直接使用java.exe命令來(lái)運(yùn)行某個(gè)主類(lèi)
4. 使用
- new出對(duì)象程序中使用
5. 卸載
- 執(zhí)行垃圾回收
2.2.3 幾種情況下會(huì)結(jié)束生命周期
- 執(zhí)行System.exit()方法
- 程序正常執(zhí)行結(jié)束
- 程序執(zhí)行過(guò)程中遇到了異常或錯(cuò)誤而異常終止
- 由于操作系統(tǒng)出現(xiàn)錯(cuò)誤而導(dǎo)致Java虛擬機(jī)進(jìn)程終止
2.3 類(lèi)加載器
1. 幾種加載器的層次關(guān)系

2. 兩種角度分類(lèi)
2.1 JVM角度
啟動(dòng)類(lèi)加載器
- 使用C++實(shí)現(xiàn)(僅限Hotspot)
- 虛擬機(jī)自身一部分
所有其他類(lèi)加載器
- Java語(yǔ)言實(shí)現(xiàn)
- 獨(dú)立虛擬機(jī)之外
- 繼承抽象類(lèi)java.lang.ClassLoader
- 需由啟動(dòng)類(lèi)加載器加載到內(nèi)存中才能加載其他的類(lèi)
2.2 開(kāi)發(fā)人員角度
啟動(dòng)類(lèi)加載器
- Bootstrap ClassLoader,C++實(shí)現(xiàn)
- 負(fù)責(zé)加載存放在JDK\jre\lib下,或被-Xbootclasspath參數(shù)指定的路徑中,且能被JVM識(shí)別的類(lèi)庫(kù)(如rt.jar,所有java.開(kāi)頭的類(lèi))
- 無(wú)法被Java程序直接引用
擴(kuò)展類(lèi)加載器
- Extension ClassLoader,由sun.misc.launcher$ExtClassLoader實(shí)現(xiàn)
- 負(fù)責(zé)加載JDK\jre\lib\ext目錄中,或java.ext.dirs系統(tǒng)變量指定的路徑中的所有類(lèi)庫(kù)(如javax.開(kāi)頭的類(lèi))
- 開(kāi)發(fā)者可直接使用擴(kuò)展類(lèi)加載器
應(yīng)用程序類(lèi)加載器
- Application ClassLoader,由sun.misc.Launcher$AppClassLoader實(shí)現(xiàn)
- 負(fù)載加載用戶(hù)類(lèi)路徑(ClassPath)所指定的類(lèi)
- 開(kāi)發(fā)者可直接使用該類(lèi)加載器,若應(yīng)用程序未自定義自己的類(lèi)加載器,此即為默認(rèn)的類(lèi)加載器
3. JVM類(lèi)加載機(jī)制(重要)
全盤(pán)負(fù)責(zé)
當(dāng)一個(gè)類(lèi)加載器加載類(lèi)時(shí),此類(lèi)所依賴(lài)和引用的其它Class都由此加載器負(fù)責(zé),除非顯示使用另一個(gè)類(lèi)加載器父類(lèi)委托
先讓父類(lèi)加載器試圖加載,只有父類(lèi)無(wú)法加載,才嘗試從自己的類(lèi)路徑加載該類(lèi)緩存機(jī)制
保證所有加載過(guò)的Class都被緩存,程序需使用時(shí)優(yōu)先從緩存中加載,沒(méi)有先找二進(jìn)制數(shù)據(jù)在堆中創(chuàng)建Class對(duì)象,加入緩存,所以每次Class修改需重啟JVM,修改才生效
2.4 類(lèi)的加載
有三種方式
- 命令行啟動(dòng)應(yīng)用時(shí)候由JVM初始化加載
- 通過(guò)Class.forName()方法動(dòng)態(tài)加載
- 通過(guò)ClassLoader.loadClass()方法動(dòng)態(tài)加載
Class.forName()與ClassLoader.loadClass()加載的區(qū)別?
- Class.forName():將類(lèi)的.class文件加載到JVM中之外,還會(huì)對(duì)類(lèi)進(jìn)行解釋?zhuān)瑘?zhí)行類(lèi)中的static塊
- ClassLoader.loadClass():只干一件事,就是將.class文件加載到JVM中,不執(zhí)行statiic中內(nèi)容(newInstance才會(huì)執(zhí)行)
- Class.forName(name,initialize,loader)帶參函數(shù)也可控制是否加載static塊,并且只有調(diào)用newInstance()方法采用調(diào)用構(gòu)造函數(shù),創(chuàng)建類(lèi)的對(duì)象
2.5 雙親委派模型(重要)
工作流程
若類(lèi)加載器收到類(lèi)加載的請(qǐng)求,首先把請(qǐng)求委托父加載器去完成,依次向上,因此所有的類(lèi)加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類(lèi)加載器中,只有當(dāng)父加載器在它的搜索范圍內(nèi)沒(méi)有找到所需的類(lèi)時(shí)(無(wú)法加載),子加載器才嘗試自己去加載該類(lèi)
雙親委派機(jī)制
- 當(dāng)AppClassLoader加載一個(gè)class時(shí),先委派ExtClassLoader加載
- 當(dāng)ExtClassLoader加載一個(gè)class時(shí),先委派BootStrapClassLoader去加載
- 若BootStrapClassLoader加載失?。↗DK\jre\lib無(wú)法找到該類(lèi)),使用ExtClassLoader加載
- ExtClassLoader也加載失?。↗DK\jre\lib\ext無(wú)法找到該類(lèi)),則由AppClassLoader加載,若還失敗則報(bào)出異常ClassNotFoundException
源碼分析
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判斷該類(lèi)型是否已經(jīng)被加載
Class c = findLoadedClass(name);
if (c == null) {
//如果沒(méi)有被加載,就委托給父類(lèi)加載或者委派給啟動(dòng)類(lèi)加載器加載
try {
if (parent != null) {
//如果存在父類(lèi)加載器,就委派給父類(lèi)加載器加載
c = parent.loadClass(name, false);
} else {
//如果不存在父類(lèi)加載器,就檢查是否是由啟動(dòng)類(lèi)加載器加載的類(lèi),
//通過(guò)調(diào)用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父類(lèi)加載器和啟動(dòng)類(lèi)加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
意義
- 系統(tǒng)類(lèi)防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
- 保證Java程序安全穩(wěn)定運(yùn)行
JDK1.9后的委派模型

2.6 自定義類(lèi)加載器
一般情況,由三種類(lèi)加載器互相配合進(jìn)行加載,如有必要,也可加入自定義類(lèi)加載器
- 如應(yīng)用是通過(guò)網(wǎng)絡(luò)來(lái)傳輸Java類(lèi)的字節(jié)碼,為保證安全性,這些字節(jié)碼經(jīng)過(guò)加密處理,系統(tǒng)類(lèi)加載器無(wú)法加載,則需要自定義加載器實(shí)現(xiàn)
- 一般繼承l(wèi)oadClasser類(lèi),只需重寫(xiě)findClass方法即可
- 核心在于對(duì)字節(jié)碼文件的獲取,如果加密的字節(jié)碼則需要在該類(lèi)中對(duì)文件進(jìn)行解密
- 傳遞的文件名是全限定性名稱(chēng)
- 最好不要重寫(xiě)loadClass方法,容易破壞雙親委托模式
- 不要把類(lèi)放在AppClassLoader路徑下,容易導(dǎo)致AppClassLoader類(lèi)加載器加載
因JVM自帶ClassLoader只懂從本地文件加載標(biāo)準(zhǔn)java cass文件,若編寫(xiě)自己的ClassLoader,需做到以下三點(diǎn)
- 執(zhí)行非置信代碼之前,自動(dòng)驗(yàn)證數(shù)字簽名
- 動(dòng)態(tài)地創(chuàng)建符合用戶(hù)特定需要的定制化構(gòu)建類(lèi)
- 從特定的場(chǎng)所所取得java class,如從數(shù)據(jù)庫(kù)中和網(wǎng)絡(luò)中