類加載器
在加載階段:通過一個(gè)類的全限定名來(lái)獲取其定義的二進(jìn)制字節(jié)流這一步會(huì)在虛擬機(jī)外部實(shí)現(xiàn)李破,以便讓應(yīng)用程序自己選擇所需要的類瘪弓,實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊就成為:“類加載器”垫蛆。
類加載器雖然只用于實(shí)現(xiàn)類的加載動(dòng)作,但還有一些其他的作用腺怯。
每個(gè)類加載器都擁有一個(gè)獨(dú)立的類名稱空間月褥,因此如果要比較兩個(gè)類是否相等,必須要保證這兩個(gè)類是由同一個(gè)類加載器加載的前提下才能進(jìn)行確定瓢喉,否則即便是同一個(gè)Class文件的兩個(gè)類宁赤,被同一個(gè)虛擬機(jī)加載,但是加載它們的類加載器不同栓票,那么這兩個(gè)類也是不同的决左。
從Java虛擬機(jī)的角度來(lái)講,只有兩種不同的類加載器:
①啟動(dòng)類加載器(Bootstrap ClassLoader)走贪,這個(gè)類加載器是由C語(yǔ)言編寫的佛猛,是屬于虛擬機(jī)的一部分;
②所有其他的類加載器坠狡,這些類加載器都屬于Java語(yǔ)言編寫的继找,獨(dú)立于虛擬機(jī)外部,并且全部繼承抽象類Java.lang.ClassLoader
從Java角度來(lái)看逃沿,類加載器還可以劃分的細(xì)致一點(diǎn)
①啟動(dòng)類加載器(Bootstrap ClassLoader):負(fù)責(zé)加載存放在<JAVA_HOME>\lib目錄中婴渡,或被 -Xbootclasspath參數(shù)指定的路徑中的,并且能被虛擬機(jī)識(shí)別的類庫(kù)(如rt.jar凯亮,所有的java.開頭的類均被 BootstrapClassLoader加載)边臼。啟動(dòng)類加載器是無(wú)法被Java程序直接引用的。
②擴(kuò)展類加載器(Extension ClassLoader):該加載器由 sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)假消,它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中柠并,或者由 java.ext.dirs系統(tǒng)變量指定的路徑中的所有類庫(kù)(如javax.開頭的類),開發(fā)者可以直接使用擴(kuò)展類加載器富拗。
③應(yīng)用程序類加載器(Application ClassLoader):該類加載器由 sun.misc.Launcher$AppClassLoader來(lái)實(shí)現(xiàn)臼予,是ClassLoader中g(shù)etSystemClassLoader()方法的返回值,一般稱為系統(tǒng)類加載器啃沪。它負(fù)責(zé)加載用戶類路徑(ClassPath)所指定的類粘拾,開發(fā)者可以直接使用該類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器谅阿,一般情況下這個(gè)就是程序中默認(rèn)的類加載器半哟。
我們看一個(gè)尋找類加載器的小示例
public class Client {
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader); //①
System.out.println(loader.getParent()); //②
System.out.println(loader.getParent().getParent()); //③
}
}
-----output-----
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@46fbb2c1
null
①是應(yīng)用程序類加載器(Application ClassLoader),它的父類②是擴(kuò)展類加載器(Extension ClassLoader)签餐,③并沒有返回?cái)U(kuò)展類加載器的父類啟動(dòng)類加載器寓涨,而是返回null,這是因?yàn)閱?dòng)類加載器(Bootstrap ClassLoader)是由C語(yǔ)言編寫氯檐,找不到一個(gè)確定的返回父Loader的方式戒良,于是就返回null。
應(yīng)用程序都是由這三種類加載器互相配合進(jìn)行加載的冠摄,如果有必要糯崎,我們還可以加入自定義的類加載器。因?yàn)镴VM自帶的ClassLoader只是懂得從本地文件系統(tǒng)加載標(biāo)準(zhǔn)的java class文件河泳,因此如果編寫了自己的ClassLoader沃呢,便可以做到如下幾點(diǎn):
- 1、在執(zhí)行非置信代碼之前拆挥,自動(dòng)驗(yàn)證數(shù)字簽名薄霜。
- 2、動(dòng)態(tài)地創(chuàng)建符合用戶特定需要的定制化構(gòu)建類纸兔。
- 3惰瓜、從特定的場(chǎng)所取得java class,例如數(shù)據(jù)庫(kù)中和網(wǎng)絡(luò)中汉矿。
這幾種類加載器的層次關(guān)系如下圖所示崎坊,該圖所展示的層次關(guān)系稱為“雙親委派模型”:
注:這里父類加載器并不是通過繼承關(guān)系來(lái)實(shí)現(xiàn)的,而是采用組合實(shí)現(xiàn)的洲拇。
雙親委派模型(Parents Delegation Model)
上圖中展示的就是雙親委派模型關(guān)系奈揍,要求最頂層必須是啟動(dòng)類加載器,除此之外每個(gè)類加載器都必須要有自己的父類加載器赋续,這里的父子類并不是以繼承的關(guān)系實(shí)現(xiàn)打月,而是以組合的方式復(fù)用父加載器的代碼。
雙親委派模型的工作過程
如果一個(gè)類加載器收到了類加載的請(qǐng)求蚕捉,它首先不會(huì)自己去嘗試加載這個(gè)類奏篙,而是把請(qǐng)求委托給父加載器去完成,依次向上迫淹,因此秘通,所有的類加載請(qǐng)求最終都應(yīng)該被傳遞到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器在它的搜索范圍中沒有找到所需的類時(shí)敛熬,即無(wú)法完成該加載肺稀,子加載器才會(huì)嘗試自己去加載該類。
雙親委派機(jī)制:
- 1应民、當(dāng) AppClassLoader加載一個(gè)class時(shí)话原,它首先不會(huì)自己去嘗試加載這個(gè)類夕吻,而是把類加載請(qǐng)求委派給父類加載器ExtClassLoader去完成。
- 2繁仁、當(dāng) ExtClassLoader加載一個(gè)class時(shí)涉馅,它首先也不會(huì)自己去嘗試加載這個(gè)類,而是把類加載請(qǐng)求委派給BootStrapClassLoader```去完成黄虱。
- 3稚矿、如果 BootStrapClassLoader加載失敗(例如在 $JAVA_HOME/jre/lib里未查找到該class)捻浦,會(huì)使用 ExtClassLoader來(lái)嘗試加載晤揣;
- 4、若ExtClassLoader也加載失敗朱灿,則會(huì)使用 AppClassLoader來(lái)加載昧识,如果 AppClassLoader也加載失敗,則會(huì)報(bào)出異常 ClassNotFoundException盗扒。
雙親委派模型意義:
①系統(tǒng)類防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
②保證Java程序安全穩(wěn)定運(yùn)行
實(shí)現(xiàn)雙親委派模型的代碼都集中在 java.lang.ClassLoader 的 loadClass( )方法中滞诺,具體源碼如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先判斷該類型是否已經(jīng)被加載
Class<?> c = findLoadedClass(name);
if (c == null) {
//如果沒有被加載,就委托給父類加載或者委派給啟動(dòng)類加載器加載
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果存在父類加載器环疼,就委派給父類加載器加載
c = parent.loadClass(name, false);
} else {
//如果不存在父類加載器习霹,就檢查是否是由啟動(dòng)類加載器加載的類,通過調(diào)用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器拋出 ClassNotFoundException炫隶,那么說明父類加載器無(wú)法完成加載請(qǐng)求
}
if (c == null) {
// 在父類加載器無(wú)法完成加載時(shí)淋叶,調(diào)用自身的加載功能 findClass(name)
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
如果我們需要自定義自己的類加載器,從上面對(duì) loadClass方法來(lái)分析來(lái)看伪阶,我們只需要重寫 findClass 方法即可煞檩。
擴(kuò)展閱讀
JVM類加載機(jī)制
- 全盤負(fù)責(zé),當(dāng)一個(gè)類加載器負(fù)責(zé)加載某個(gè)Class時(shí)栅贴,該Class所依賴的和引用的其他Class也將由該類加載器負(fù)責(zé)載入斟湃,除非顯示使用另外一個(gè)類加載器來(lái)載入
- 父類委托,先讓父類加載器試圖加載該類檐薯,只有在父類加載器無(wú)法加載該類時(shí)才嘗試從自己的類路徑中加載該類
- 緩存機(jī)制凝赛,緩存機(jī)制將會(huì)保證所有加載過的Class都會(huì)被緩存,當(dāng)程序中需要使用某個(gè)Class時(shí)坛缕,類加載器先從緩存區(qū)尋找該Class墓猎,只有緩存區(qū)不存在,系統(tǒng)才會(huì)讀取該類對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)赚楚,并將其轉(zhuǎn)換成Class對(duì)象毙沾,存入緩存區(qū)。這就是為什么修改了Class后宠页,必須重啟JVM左胞,程序的修改才會(huì)生效
Class.forName()和ClassLoader.loadClass()區(qū)別
- Class.forName():將類的.class文件加載到j(luò)vm中之外寇仓,還會(huì)對(duì)類進(jìn)行解釋,執(zhí)行類中的static塊烤宙;
- ClassLoader.loadClass():只干一件事情遍烦,就是將.class文件加載到j(luò)vm中,不會(huì)執(zhí)行static中的內(nèi)容,只有在newInstance才會(huì)去執(zhí)行static塊门烂。
- Class.forName(name,initialize,loader)帶參函數(shù)也可控制是否加載static塊乳愉。并且只有調(diào)用了newInstance()方法采用調(diào)用構(gòu)造函數(shù)兄淫,創(chuàng)建類的對(duì)象 屯远。