對(duì)于任意一個(gè)類(lèi),都需要由加載它的類(lèi)加載器和這個(gè)類(lèi)本身一同確立其在Java虛擬機(jī)中的唯一性车吹,每一個(gè)類(lèi)加載器眷蚓,都擁有一個(gè)獨(dú)立的類(lèi)名稱(chēng)空間。這句話可以表達(dá)得更通俗一些:比較兩個(gè)類(lèi)是否“相等”立倍,只有在這兩個(gè)類(lèi)是由同一個(gè)類(lèi)加載器加載的前提下才有意義,否則侣滩,即使這兩個(gè)類(lèi)來(lái)源于同一個(gè)Class文件口注,被同一個(gè)虛擬機(jī)加載,只要加載它們的類(lèi)加載器不同君珠,那這兩個(gè)類(lèi)就必定不相等疆导。
雙親委派模型
從Java虛擬機(jī)的角度來(lái)講,只存在兩種不同的類(lèi)加載器:一種是啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader)葛躏,這個(gè)類(lèi)加載器使用C++語(yǔ)言實(shí)現(xiàn)澈段,是虛擬機(jī)自身的一部分;另一種就是所有其他的類(lèi)加載器舰攒,這些類(lèi)加載器都由Java語(yǔ)言實(shí)現(xiàn)败富,獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類(lèi)java.lang.ClassLoader摩窃。從Java開(kāi)發(fā)人員的角度來(lái)看兽叮,類(lèi)加載器還可以劃分得更細(xì)致一些,絕大部分Java程序都會(huì)使用到以下3種系統(tǒng)提供的類(lèi)加載器猾愿。
從Java開(kāi)發(fā)人員的角度來(lái)看鹦聪,類(lèi)加載器還可以劃分得更細(xì)致一些,絕大部分Java程序都會(huì)使用到以下3種系統(tǒng)提供的類(lèi)加載器蒂秘。
啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader)這個(gè)類(lèi)將器負(fù)責(zé)將存放在<JAVA_HOME>\lib目錄中的泽本,或者被-Xbootclasspath參數(shù)所指定的路徑中的,并且是虛擬機(jī)識(shí)別的(僅按照文件名識(shí)別姻僧,如rt.jar规丽,名字不符合的類(lèi)庫(kù)即使放在lib目錄中也不會(huì)被加載)類(lèi)庫(kù)加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類(lèi)加載器無(wú)法被Java程序直接引用撇贺,用戶在編寫(xiě)自定義類(lèi)加載器時(shí)赌莺,如果需要把加載請(qǐng)求委派給引導(dǎo)類(lèi)加載器,那直接使用null代替即可松嘶。
擴(kuò)展類(lèi)加載器(Extension ClassLoader):這個(gè)加載器由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn)艘狭,它負(fù)責(zé)加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用擴(kuò)展類(lèi)加載器巢音。
- 應(yīng)用程序類(lèi)加載器(Application ClassLoader):這個(gè)類(lèi)加載器由sun.misc.Launcher$App-ClassLoader實(shí)現(xiàn)鼓鲁。由于這個(gè)類(lèi)加載器是ClassLoader中的getSystemClass-Loader()方法的返回值,所以一般也稱(chēng)它為系統(tǒng)類(lèi)加載器港谊。它負(fù)責(zé)加載用戶類(lèi)路徑(ClassPath)上所指定的類(lèi)庫(kù),開(kāi)發(fā)者可以直接使用這個(gè)類(lèi)加載器橙弱,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類(lèi)加載器歧寺,一般情況下這個(gè)就是程序中默認(rèn)的類(lèi)加載器。
這些類(lèi)加載器之間的關(guān)系一般如圖
圖中展示的類(lèi)加載器之間的這種層次關(guān)系棘脐,稱(chēng)為類(lèi)加載器的雙親委派模型(Parents Delegation Model)斜筐。雙親委派模型要求除了頂層的啟動(dòng)類(lèi)加載器外,其余的類(lèi)加載器都應(yīng)當(dāng)有自己的父類(lèi)加載器蛀缝。這里類(lèi)加載器之間的父子關(guān)系一般不會(huì)以繼承(Inheritance)的關(guān)系來(lái)實(shí)現(xiàn)顷链,而是都使用組合(Composition)關(guān)系來(lái)復(fù)用父加載器的代碼。
雙親委派模型的工作過(guò)程是:
如果一個(gè)類(lèi)加載器收到了類(lèi)加載的請(qǐng)求屈梁,它首先不會(huì)自己去嘗試加載這個(gè)類(lèi)嗤练,而是把這個(gè)請(qǐng)求委派給父類(lèi)加載器去完成,每一個(gè)層次的類(lèi)加載器都是如此在讶,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類(lèi)加載器中煞抬,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成這個(gè)加載請(qǐng)求(它的搜索范圍中沒(méi)有找到所需的類(lèi))時(shí),子加載器才會(huì)嘗試自己去加載构哺。
使用雙親委派模型來(lái)組織類(lèi)加載器之間的關(guān)系革答,有一個(gè)顯而易見(jiàn)的好處就是Java類(lèi)隨著它的類(lèi)加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類(lèi)java.lang.Object曙强,它存放在rt.jar之中残拐,無(wú)論哪一個(gè)類(lèi)加載器要加載這個(gè)類(lèi),最終都是委派給處于模型最頂端的啟動(dòng)類(lèi)加載器進(jìn)行加載碟嘴,因此Object類(lèi)在程序的各種類(lèi)加載器環(huán)境中都是同一個(gè)類(lèi)溪食。
破壞雙親委派模型
上文提到過(guò)雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java設(shè)計(jì)者推薦給開(kāi)發(fā)者的類(lèi)加載器實(shí)現(xiàn)方式娜扇。
雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前——即JDK 1.2發(fā)布之前眠菇。由于雙親委派模型在JDK 1.2之后才被引入,而類(lèi)加載器和抽象類(lèi)java.lang.ClassLoader則在JDK 1.0時(shí)代就已經(jīng)存在袱衷,面對(duì)已經(jīng)存在的用戶自定義類(lèi)加載器的實(shí)現(xiàn)代碼捎废,Java設(shè)計(jì)者引入雙親委派模型時(shí)不得不做出一些妥協(xié)。為了向前兼容致燥,JDK 1.2之后的java.lang.ClassLoader添加了一個(gè)新的protected方法findClass()登疗,在此之前,用戶去繼承java.lang.ClassLoader的唯一目的就是為了重寫(xiě)loadClass()方法,因?yàn)樘摂M機(jī)在進(jìn)行類(lèi)加載的時(shí)候會(huì)調(diào)用加載器的私有方法loadClassInternal()辐益,而這個(gè)方法的唯一邏輯就是去調(diào)用自己的loadClass()断傲。上一節(jié)我們已經(jīng)看過(guò)loadClass()方法的代碼,雙親委派的具體邏輯就實(shí)現(xiàn)在這個(gè)方法之中智政,JDK 1.2之后已不提倡用戶再去覆蓋loadClass()方法认罩,而應(yīng)當(dāng)把自己的類(lèi)加載邏輯寫(xiě)到findClass()方法中,在loadClass()方法的邏輯里如果父類(lèi)加載失敗续捂,則會(huì)調(diào)用自己的findClass()方法來(lái)完成加載垦垂,這樣就可以保證新寫(xiě)出來(lái)的類(lèi)加載器是符合雙親委派規(guī)則的。
雙親委派模型的第二次“被破壞”是由這個(gè)模型自身的缺陷所導(dǎo)致的牙瓢,雙親委派很好地解決了各個(gè)類(lèi)加載器的基礎(chǔ)類(lèi)的統(tǒng)一問(wèn)題(越基礎(chǔ)的類(lèi)由越上層的加載器進(jìn)行加載)劫拗,基礎(chǔ)類(lèi)之所以稱(chēng)為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸挥脩舸a調(diào)用的API矾克,但世事往往沒(méi)有絕對(duì)的完美页慷,如果基礎(chǔ)類(lèi)又要調(diào)用回用戶的代碼,那該怎么辦胁附?
為了解決這個(gè)問(wèn)題酒繁,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類(lèi)加載器(Thread Context ClassLoader)。這個(gè)類(lèi)加載器可以通過(guò)java.lang.Thread類(lèi)的setContextClass-Loaser()方法進(jìn)行設(shè)置控妻,如果創(chuàng)建線程時(shí)還未設(shè)置欲逃,它將會(huì)從父線程中繼承一個(gè),如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò)的話饼暑,那這個(gè)類(lèi)加載器默認(rèn)就是應(yīng)用程序類(lèi)加載器稳析。
JNDI服務(wù)使用這個(gè)線程上下文類(lèi)加載器去加載所需要的SPI代碼,也就是父類(lèi)加載器請(qǐng)求子類(lèi)加載器去完成類(lèi)加載的動(dòng)作弓叛,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類(lèi)加載器彰居,實(shí)際上已經(jīng)違背了雙親委派模型的一般性原則,但這也是無(wú)可奈何的事情撰筷。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式陈惰,例如JNDI、JDBC毕籽、JCE抬闯、JAXB和JBI等。雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序動(dòng)態(tài)性的追求而導(dǎo)致的关筒,這里所說(shuō)的“動(dòng)態(tài)性”指的是當(dāng)前一些非橙芪眨“熱門(mén)”的名詞:代碼熱替換(HotSwap)、模塊熱部署(Hot De-ployment)等蒸播,
OSGi已經(jīng)成為了業(yè)界“事實(shí)上”的Java模塊化標(biāo)準(zhǔn)睡榆,而OSGi實(shí)現(xiàn)模塊化熱部署的關(guān)鍵則是它自定義的類(lèi)加載器機(jī)制的實(shí)現(xiàn)萍肆。每一個(gè)程序模塊(OSGi中稱(chēng)為Bundle)都有一個(gè)自己的類(lèi)加載器,當(dāng)需要更換一個(gè)Bundle時(shí)胀屿,就把Bundle連同類(lèi)加載器一起換掉以實(shí)現(xiàn)代碼的熱替換塘揣。
在OSGi環(huán)境下,類(lèi)加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu)宿崭,而是進(jìn)一步發(fā)展為更加復(fù)雜的網(wǎng)狀結(jié)構(gòu)亲铡,當(dāng)收到類(lèi)加載請(qǐng)求時(shí),OSGi將按照下面的順序進(jìn)行類(lèi)搜索:
1)將以java.*開(kāi)頭的類(lèi)委派給父類(lèi)加載器加載葡兑。
2)否則奖蔓,將委派列表名單內(nèi)的類(lèi)委派給父類(lèi)加載器加載。
3)否則铁孵,將Import列表中的類(lèi)委派給Export這個(gè)類(lèi)的Bundle的類(lèi)加載器加載。
4)否則房资,查找當(dāng)前Bundle的ClassPath蜕劝,使用自己的類(lèi)加載器加載。
5)否則轰异,查找類(lèi)是否在自己的Fragment Bundle中岖沛,如果在,則委派給Fragment Bundle的類(lèi)加載器加載搭独。
6)否則婴削,查找Dynamic Import列表的Bundle,委派給對(duì)應(yīng)Bundle的類(lèi)加載器加載牙肝。
7)否則唉俗,類(lèi)查找失敗。上面的查找順序中只有開(kāi)頭兩點(diǎn)仍然符合雙親委派規(guī)則配椭,其余的類(lèi)查找都是在平級(jí)的類(lèi)加載器中進(jìn)行的虫溜。