一、類加載器
1. 作用
實現(xiàn) 通過一個類的全限定名來獲取描述該類的二進制字節(jié)流 動作仔涩,即類的加載動作。
在虛擬機中粘舟,每個類加載器都有一個獨立的類名稱空間熔脂,故只有在 兩個類的類的全限定名相同佩研,且加載該類的加載器相同 的情況下,才判定相等(包括 equals()
霞揉、isAssignableFrom()
旬薯、isInstance()
方法及 instanceOf
關(guān)鍵字的判斷結(jié)果)。
2. 分類
啟動類加載器 (Bootstrap Class Loader)
負責(zé)加載存放在 <JAVA_HOME>\lib
目錄适秩,或者被 -Xbootclasspath 參數(shù)所指定的路徑中存放的绊序,且文件名能被識別的類庫(如 rt.jar
、tools.jar
秽荞,文件名不符合目錄正確也不會被加載)加載到 JVM 內(nèi)存中骤公。此加載器無法被 Java 程序直接使用,自定義類加載器若需要委派加載請求給此加載器加載蚂会,直接使用 null 代替即可淋样。
擴展類加載器(Extension Class Loader)
負責(zé)加載 <JAVA_HOME>\lib\ext
目錄中,或者被 java.ext.dirs
系統(tǒng)變量所指定路徑中的所有類庫胁住。此類庫中存放具有通用性的擴展類庫趁猴,且允許用戶自行添加,即擴展機制彪见。在類 sun.misc.Launcher$ExtClassLoader
中以 Java 代碼形式實現(xiàn)儡司,故用戶可直接在程序中使用此類加載器加載 Class 文件。JDK 9 中余指,此擴展類加載器被平臺類加載器替代捕犬。
平臺類加載器(Platform Class Loader)
由于模塊化系統(tǒng)中,整個 JDK 都基于模塊化構(gòu)建酵镜,故Java 類庫為滿足模塊化需求碉碉,未保留 <JAVA_HOME>\lib\ext
目錄,擴展類加載器也被替換為平臺類加載器淮韭。
應(yīng)用程序類加載器(Application Class Loader)
負責(zé)加載用戶類路徑(ClassPath)上所有類庫垢粮,開發(fā)者可直接使用此類加載器。由于此加載器在 ClassLoader 類中是方法getSystemClassLoader()
的返回值靠粪,故又稱系統(tǒng)類加載器蜡吧。若用戶未自定義加載器,一般情況下為默認加載器占键。
自定義類加載器
可通過重寫 ClassLoader 類的 findClass()
方法實現(xiàn)自定義類加載器昔善,以完成某些功能。
二畔乙、雙親委派模型
1. 描述
如果一個類加載器收到了類加載的請求君仆,它不會加載自己嘗試加載此類,而是委派請求給父類加載器進行加載。
2. 意義
共享
使 Java 類隨著它的類加載器一起具備了一種 帶有優(yōu)先級的層次關(guān)系返咱,通過這種層級關(guān)可以避免類的重復(fù)加載氮帐,當(dāng)父類加載器已經(jīng)加載了該類時,子 ClassLoader 就沒有必要再加載一次洛姑。
隔離
隔離功能,保證核心類庫的純凈和安全皮服,防止惡意加載楞艾,避免了 Java 的核心 API 被篡改。
保證唯一
若不采用雙親委派機制龄广,同一個類有可能被多個類加載器加載硫眯,這樣該類會被識別為兩個不同的類。
雙親委派機制在很大程度上防止內(nèi)存中出現(xiàn)多個相同的字節(jié)碼文件择同,加載類的時候默認會使用當(dāng)前類的 ClassLoader 進行加載两入,只有當(dāng)你使用該 class 的時候才會去裝載,一個加載器只會裝載同一個 class 一次敲才。
3. 源碼
源碼比較簡單裹纳,全部集中在 java.lang.ClassLoader
的 loadClass()
方法中。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先紧武,檢查請求類是否被加載過了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器拋出 ClassNotFoundException剃氧,說明父類加載器無法完成加載請求
// from the non-null parent class loader
}
if (c == null) {
// 在父類加載器無法加載時,再調(diào)用本身的 findClass 方法進行類加載
long t1 = System.nanoTime();
c = findClass(name);
// 記錄統(tǒng)級信息
...
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
先檢查請求加載的類型是否已加載過阻星,若沒有則調(diào)用父加載器的 loadClass()
方法朋鞍;若父類加載器為空則默認使用啟動類作為父加載器。若父類加載器加載失敗拋出 ClassNotFoundException
異常妥箕,才調(diào)用自己的 findClass()
方法嘗試進行加載滥酥。
4. 雙親委派模型圖
如圖,即為 JDK 1.8 及以前的雙親委派模型圖畦幢,除頂層類加載器外坎吻,其余類加載器都必須有自己的父類加載器。類加載器的父子關(guān)系一般 不以繼承關(guān)系 實現(xiàn)呛讲,而是 組合關(guān)系 復(fù)用父加載器代碼禾怠。
JDK 9 中因模塊化的加入而重構(gòu)了目錄結(jié)構(gòu),也順帶將擴展類加載器替換為平臺類加載器贝搁。雖然總體上仍保持三層類加載器和雙親委派架構(gòu)吗氏,但委派關(guān)系發(fā)生變動。
當(dāng)平臺及應(yīng)用程序類加載器收到類加載請求雷逆,在委派給父類加載器前弦讽,要先 判斷是否歸屬于某一個系統(tǒng)模塊,如果找到歸屬關(guān)系,則優(yōu)先委派給對應(yīng)模塊的類加載器往产。由此被碗,也可以算是類加載器的 第四次被破壞。
三仿村、雙親委派模型的三次破壞
雙親委派模型僅僅是 Java 設(shè)計者推薦開發(fā)者們的類加載器實現(xiàn)方式锐朴,并不是強制約束的模型。截至目前蔼囊,大多數(shù) Java 圈的類加載器都遵循此模型焚志,但仍出現(xiàn)過三次較大規(guī)模破壞。
1. 兼容 JDK 1.2 前的程序
產(chǎn)生原因
由于雙親委派模型是 JDK 1.2 才被引入畏鼓,但 Java 第一個版本即存在抽象類 java.lang.ClassLoader
酱酬,開發(fā)者已編寫好自定義類加載器,若進行 JDK 升級云矫,則會導(dǎo)致 loadClass()
被覆蓋膳沽,而正確做法應(yīng)為重寫 findClass()
。
解決方案
為了兼容已存在的用戶自定義類让禀,Java 設(shè)計者們只能在 JDK 1.2 后添加一個新的 protected
方法 findClass()
挑社,并引導(dǎo)用戶盡可能在類加載邏輯中重寫此方法,而不是在 loadClass()
中編寫代碼巡揍。按照 loadClass()
方法邏輯滔灶,父類加載失敗,會調(diào)用自己的 findClass()
方法完成加載吼肥,保證新代碼也可以符號雙親委派規(guī)則录平。
2. 自身的缺陷
產(chǎn)生原因
雙親委派模型很好地解決了各個類加載器協(xié)作時基礎(chǔ)類型一致性問題,即越基礎(chǔ)的類由越上層的類加載器加載缀皱。但基礎(chǔ)類型也會存在調(diào)用回用戶代碼的場景斗这。
場景
典型的例子便是 JNDI 服務(wù),此服務(wù)已是 Java 標準服務(wù)啤斗。它的代碼由啟動類加載器完成加載(即在 rt.jar
中)表箭,屬于 Java 中很基礎(chǔ)的類型。但 JNDI 存在的目的就是對資源進行查找和集中管理钮莲,故需要調(diào)用其他廠商實現(xiàn)并部署在應(yīng)用程序 ClassPath 下的 JNDI 服務(wù)提供者接口(SPI)免钻,但啟動類不可能識別且加載這些代碼。
解決方案
為解決此問題崔拥,Java 設(shè)計團隊引入了線程上下文類加載器极舔。此加載器可通過 java.lang.Thread
類的 setContextClassLoader()
方法進行設(shè)置,如果創(chuàng)建線程時未設(shè)置链瓦,它將從父類繼承一個拆魏,如果應(yīng)用程序全局范圍內(nèi)都未設(shè)置盯桦,則這個類加載器默認為應(yīng)用程序類加載器。故以此即可加載所需的 SPI 服務(wù)代碼渤刃。此方式為一種父類加載器請求子類加載器完成類加載的行為拥峦。
3. 實現(xiàn)程序動態(tài)性
產(chǎn)生原因
程序動態(tài)性即代碼熱替換、模塊熱部署等功能卖子。
場景
OSGi 是實現(xiàn)熱部署的常用規(guī)范略号,其實現(xiàn)熱部署的關(guān)鍵是它自定義的類加載器機制的實現(xiàn),每一個程序模塊(在 OSGi 中稱為 Bundle)都有一個類加載器洋闽,當(dāng)需要更換一個 Bundle 時璃哟,就把 Bundle 連同類加載器一起換掉以實現(xiàn)代碼熱替換。
實現(xiàn)方式
在 OSGi 環(huán)境下喊递,類加載器不再是雙親委派模型推薦的樹狀結(jié)構(gòu),而是更加復(fù)雜的網(wǎng)狀結(jié)構(gòu)阳似,它的委派關(guān)系僅少部分遵守雙親委派模型骚勘,其余部分會在平級類加載器中查找。