## 類與類加載器
類加載器只用于實(shí)現(xiàn)類加載的動(dòng)作,但是同時(shí)還有著確保類的唯一性的作用。也就是說:比較兩個(gè)類是否相等,只有確保這兩個(gè)類在同一個(gè)類加載器的前提下才是有意義的。否則即使兩個(gè)類來源于同一個(gè)klass文件壕吹,被同一個(gè)jvm加載,只要他們的類加載器不同删铃,那么這兩個(gè)類就不相等(這里的相等代表klass對(duì)象的equals耳贬、isAssignableFrom、isInstance方法返回的結(jié)果)猎唁。
雙親委派模式
類加載器的種類
對(duì)于jvm來講咒劲,類加載器分為兩種:
一種是啟動(dòng)類加載器,使用c++實(shí)現(xiàn)诫隅,是jvm自身的一部分
另一種是所有其他的類加載器腐魂,使用java實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部逐纬,并且繼承自ClassLoader蛔屹。
然而對(duì)于java開發(fā)人員來講,類加載器可以分成這三種:
- 啟動(dòng)類加載器
- 擴(kuò)展類加載器
- 應(yīng)用程序類加載器豁生。這個(gè)類加載器也被稱為
系統(tǒng)類加載器
兔毒,負(fù)責(zé)加載用戶路徑上的類庫,開發(fā)者可以直接使用這個(gè)類加載器沛硅。
對(duì)于jvm加載一個(gè)類而言眼刃,是通過上面的三種類加載器相互配合加載的绕辖,他們之間的關(guān)系如下圖:
上圖中展示的這種一層層的層次加載關(guān)系摇肌,被稱為類的雙親委派模式。的雙親委派要求除了頂層的類加載器外都有自己的父類加載器仪际。這里的類加載器之間的父子關(guān)系一般都不會(huì)以繼承的關(guān)系來實(shí)現(xiàn)围小,而是組合關(guān)系來復(fù)用類加載器的代碼。
整個(gè)雙親委派模式的過程是:
如果一個(gè)類加載器收到了類加載請(qǐng)求树碱,他不會(huì)嘗試自己去加載肯适,而是吧這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此成榜,因此所有類加載的請(qǐng)求最終都應(yīng)該傳到頂層的啟動(dòng)類加載器中框舔,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)類加載的請(qǐng)求(它的搜索范圍中沒有找到所需的類)時(shí),子類才會(huì)嘗試自己加載。
為什么要使用雙親委派模式:因?yàn)檫@樣加載一個(gè)類刘绣,java類隨著他的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系樱溉,例如java.lang.Object
他放在rt.jar
中。無論哪個(gè)類加載器加載這個(gè)類纬凤,最后都是委派給最頂端的啟動(dòng)類加載器去加載福贞,這樣就保證了java.lang.Object
無論是由哪個(gè)類加載器加載的,在當(dāng)前jvm下都是同一個(gè)類停士。
破壞雙親委派模型
第一次破壞是在jdk2之前挖帘,用戶自定義的類加載器都是重寫
Classloader
中的loadClass
方法,這樣就導(dǎo)致每個(gè)自定義的類加載器其實(shí)是在使用自己的loadClass
方法中的加載機(jī)制來進(jìn)行加載,這種模式當(dāng)然是不符合雙親委派機(jī)制的,也是無法保證同一個(gè)類在jvm中的唯一性的恋技,那么為了保證及時(shí)是由不同的類加載器(哪怕是用戶自定義的類加載器加載)也是唯一的拇舀,java官方在Classloader
中添加了findClass
方法,用戶只需要重新這個(gè)findClass
方法,在loadClass
方法的邏輯里猖任,如果父類加載失敗的時(shí)候你稚,才會(huì)調(diào)用自己的findClass
方法來完成類加載,這樣就完成了符合雙親委派機(jī)制朱躺。-
第二次的破壞是類似于jndi刁赖,jdbc這種服務(wù),因?yàn)檫@種服務(wù)需要回調(diào)用戶的代碼长搀,但是對(duì)于父類加載器而言是不認(rèn)識(shí)用戶的代碼的宇弛。
那么這時(shí)候java團(tuán)隊(duì)使用了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文類加載器。這個(gè)類加載器可以通過
Thread
類的setContextClassLoader
方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置源请,它就從父線程繼承一個(gè)枪芒,如果在應(yīng)用全局范圍內(nèi)都沒有設(shè)置過的話,那這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器谁尸。
利用這個(gè)線程上下文類加載器
,jdni去加載需要的spi代碼舅踪,也就是父類請(qǐng)求子類的加載器去加載。
- 第三次的破壞是因?yàn)橛脩魧?duì)于程序的動(dòng)態(tài)性追求良蛮,諸如:代碼熱替換抽碌,模塊熱部署。
這時(shí)候就誕生了諸如jigsaw和osgi决瞳。對(duì)于現(xiàn)在的業(yè)界來講货徙,osgi贏得了java模塊化的主導(dǎo)權(quán),成為目前業(yè)界模塊化的標(biāo)準(zhǔn)皮胡。而Osgi模塊話的關(guān)鍵是他自己的類加載機(jī)制:每個(gè)程序模塊(bundle)都有自己的類加載器痴颊,需要更換程序(bundle)的時(shí)候,連同類加載器一起替換屡贺,以實(shí)現(xiàn)代碼的熱部署
蠢棱。
osgi和雙親委派模式不同锌杀,他是一個(gè)基于網(wǎng)狀的互相組合依賴的加載。
Osgi的加載步驟是這樣的:
- 如果類或者資源是在包java.*中泻仙,那么交由父級(jí)類加載器代理完成抛丽,否則,搜索過程進(jìn)入第二步饰豺。如果父類級(jí)類加載器加載失敗亿鲜,那么查找過程結(jié)束,加載失敗冤吨。
- 如果類或者資源在啟動(dòng)代理序列(org.osgi.framework.bootdelegation)中定義蒿柳,那么交由父級(jí)代理完成,此時(shí)的父級(jí)代理有啟動(dòng)參數(shù)org.osgi.framework.bundle.parent指定漩蟆,默認(rèn)是引導(dǎo)類加載器(bootstrap class loader)垒探,如果找到了類或者資源,那么查找過程結(jié)束怠李。
- 如果類或者資源所在的包是在Import-Package中指定的圾叼,或者是在此之前通過動(dòng)態(tài)導(dǎo)入加載的了,那么將請(qǐng)求轉(zhuǎn)發(fā)到導(dǎo)出bundle的類加載器捺癞,否則搜索繼續(xù)進(jìn)行下一步夷蚊;如果該包在啟動(dòng)參數(shù)org.osgi.framework.system.packages.extra中,則將請(qǐng)求轉(zhuǎn)發(fā)給osgi容器外部的類加載器(通常是系統(tǒng)類加載器)髓介。如果將請(qǐng)求交由導(dǎo)出類加載器代理惕鼓,而類或者資源又沒有找到,那么查找過程中止唐础,同時(shí)請(qǐng)求失敗箱歧。
- 如果包中類或者和資源所在的包由其他bundle通過是使用Require-Bundle從一個(gè)或多個(gè)其他bundle進(jìn)行導(dǎo)入的了,那么請(qǐng)求交由其他那些bundle的類加載器完成一膨,按照根據(jù)在bundle的manifest中指定的順序進(jìn)行查找進(jìn)行查找呀邢。如果沒有找到類或者資源,搜索繼續(xù)進(jìn)行豹绪。
- 使用bundle本身的內(nèi)部bundle類路徑查找完畢之后价淌,。如果類或者資源還沒有找到森篷,搜索繼續(xù)到下一步输钩。
- 查找每一個(gè)附加的fragment的內(nèi)部類路徑豺型,fragment的查找根據(jù)bundle ID順序升序查找仲智。如果沒有找到類或者資源的,查找過程繼續(xù)下一步姻氨。
- 如果包中類或者資源所在的包由bundle導(dǎo)出钓辆,或者包由bundle導(dǎo)入(使用Import-Package或者Require-Bundle),查找結(jié)束,即類或者資源沒有找到前联。
- 否則功戚,如果類或者資源所在的包是通過使用DynamicImport-Package進(jìn)行導(dǎo)入,那么試圖進(jìn)行包的動(dòng)態(tài)導(dǎo)入似嗤。導(dǎo)出者exporter必須符合包約束啸臀。如果找到了合適的導(dǎo)出者exporter,然后建立連接烁落,以后的包導(dǎo)入就可以通過步驟三進(jìn)行乘粒。如果連接建立失敗,那么請(qǐng)求失敗伤塌。
- 如果動(dòng)態(tài)導(dǎo)入建立了灯萍,請(qǐng)求交由導(dǎo)出bundle的類加載器代理。如果代理查找失敗每聪,那么查找過程中止旦棉,請(qǐng)求失敗