類(lèi)與類(lèi)加載器

對(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)系一般如圖

xRQCCySIWdJV8O.jpg

圖中展示的類(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)行的虫溜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市股缸,隨后出現(xiàn)的幾起案子衡楞,更是在濱河造成了極大的恐慌,老刑警劉巖敦姻,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瘾境,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡镰惦,警方通過(guò)查閱死者的電腦和手機(jī)迷守,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)旺入,“玉大人盒犹,你說(shuō)我怎么就攤上這事。” “怎么了急膀?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵沮协,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我卓嫂,道長(zhǎng)慷暂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任晨雳,我火速辦了婚禮行瑞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘餐禁。我一直安慰自己血久,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布帮非。 她就那樣靜靜地躺著氧吐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪末盔。 梳的紋絲不亂的頭發(fā)上筑舅,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音陨舱,去河邊找鬼翠拣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛游盲,可吹牛的內(nèi)容都是我干的误墓。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼益缎,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼优烧!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起链峭,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤畦娄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后弊仪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體熙卡,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年励饵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了驳癌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡役听,死狀恐怖颓鲜,靈堂內(nèi)的尸體忽然破棺而出表窘,到底是詐尸還是另有隱情,我是刑警寧澤甜滨,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布乐严,位于F島的核電站,受9級(jí)特大地震影響衣摩,放射性物質(zhì)發(fā)生泄漏昂验。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一艾扮、第九天 我趴在偏房一處隱蔽的房頂上張望既琴。 院中可真熱鬧,春花似錦泡嘴、人聲如沸甫恩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)磺箕。三九已至,卻和暖如春霎终,著一層夾襖步出監(jiān)牢的瞬間滞磺,已是汗流浹背升薯。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工莱褒, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涎劈。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓广凸,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蛛枚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谅海,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容