類加載器

虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的 “通過一個(gè)類的全限定名來獲取描述此類的二進(jìn)制字節(jié)流(即字節(jié)碼)” 這個(gè)動(dòng)作放到 Java 虛擬機(jī)外部去實(shí)現(xiàn),以便讓應(yīng)用程序自己決定如何去獲取所需要的類(通過一個(gè)類的全限之名獲取描述此類的二進(jìn)制字節(jié)流)。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為 “類加載器”。

一、類與類加載器

”首先我們需要了解來自同一個(gè)Class文件的兩個(gè)類是否一定是“相等”的,這個(gè)相等幾乎涵蓋能代表“相等”的方法,equals()、isAssignableFrom()、isInstance(),也包括instanceof關(guān)鍵字判斷對(duì)象所屬關(guān)系。這是因?yàn)?,?duì)于任意一個(gè)類,都需要由加載它的類加載器和這個(gè)類本身一同確立在Java虛擬機(jī)的唯一性,每一個(gè)類加載器,都擁有一個(gè)獨(dú)立的類名稱空間。這句話意思就是說,比較兩個(gè)類是否相等不能光看他們本身來自同一個(gè)類,同時(shí)也要看加載它們的類加載器是否相同,只有被同一個(gè)類加載器加載的類才可能會(huì)相等。

二、類加載器分類

從 Java 虛擬機(jī)的角度來講,只存在以下兩種不同的類加載器:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader),這個(gè)類加載器用 C++ 實(shí)現(xiàn),是虛擬機(jī)自身的一部分;
  • 所有其他類的加載器,這些類由 Java 實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全都繼承自抽象類 java.lang.ClassLoader。

從 Java 開發(fā)人員的角度看,類加載器可以劃分得更細(xì)致一些:

  • 啟動(dòng)類加載器(Bootstrap ClassLoader)啟動(dòng)類加載器無法被 Java 程序直接引用,用戶在編寫自定義類加載器時(shí),如果需要把加載請(qǐng)求委派給啟動(dòng)類加載器,直接使用 null 代替即可。
  • 擴(kuò)展類加載器(Extension ClassLoader)它負(fù)責(zé)將系統(tǒng)變量所指定路徑中的所有類庫加載到內(nèi)存中,開發(fā)者可以直接使用擴(kuò)展類加載器。
  • 應(yīng)用程序類加載器(Application ClassLoader)

三、雙親委派模型

如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的加載器都是如此,因此所有的類加載請(qǐng)求都會(huì)傳給頂層的啟動(dòng)類加載器,只有當(dāng)父加載器反饋?zhàn)约簾o法完成該加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。

雙親委派模型的好處: 避免重復(fù)加載 + 避免核心類篡改

  • 在于 Java 類隨著它的類加載器一起具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如類 java.lang.Object,它存在在 rt.jar 中,無論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的 Bootstrap ClassLoader 進(jìn)行加載,因此 Object 類在程序的各種類加載器環(huán)境中都是同一個(gè)類。相反,如果沒有雙親委派模型而是由各個(gè)類加載器自行加載的話,如果用戶編寫了一個(gè) java.lang.Object 的同名類并放在ClassPath 中,那系統(tǒng)中將會(huì)出現(xiàn)多個(gè)不同的 Object 類,程序?qū)⒒靵y。因此,如果開發(fā)者嘗試編寫一個(gè)與rt.jar類庫中重名的 Java 類,可以正常編譯,但是永遠(yuǎn)無法被加載運(yùn)行。

四、破壞雙親委派模型

1. 自定義類加載器

重寫 loadClass() 方法(如果僅重寫 findClass(),仍會(huì)由啟動(dòng)類加載器加載)。

2. 線程上下文類加載器(context class loader)

Java 提供了很多服務(wù)提供者接口(Service Provider Interface,SPI),允許第三方為這些接口提供實(shí)現(xiàn)。常見的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。
這些 SPI 的接口由 Java 核心庫來提供,而這些 SPI 的實(shí)現(xiàn)代碼則是作為 Java 應(yīng)用所依賴的 jar 包被包含進(jìn)類路徑(CLASSPATH)里。SPI接口中的代碼經(jīng)常需要加載具體的實(shí)現(xiàn)類。那么問題來了,SPI的接口是Java核心庫的一部分,是由引導(dǎo)類加載器來加載的;SPI的實(shí)現(xiàn)類是由系統(tǒng)類加載器來加載的。引導(dǎo)類加載器是無法找到 SPI 的實(shí)現(xiàn)類的,因?yàn)橐勒针p親委派模型,BootstrapClassloader無法委派AppClassLoader來加載類。
而線程上下文類加載器破壞了雙親委派模型,可以在執(zhí)行線程中拋棄雙親委派加載鏈模式,使程序可以逆向使用類加載器。

Thread類中有 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl) 方法用來獲取和設(shè)置上下文類加載器,如果沒有 setContextClassLoader(ClassLoader cl) 方法通過設(shè)置類加載器,那么線程將繼承父線程的上下文類加載器,如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置的話,那么這個(gè)上下文類加載器默認(rèn)就是應(yīng)用程序類加載器(Application ClassLoader),換句話說 Java 默認(rèn)的線程上下文類加載器就是應(yīng)用程序類加載器(AppClassLoader)。通過線程上下文來加載第三方庫 JNDI 實(shí)現(xiàn),而不依賴于雙親委派。大部分 Java Application 服務(wù)器 (jboss, tomcat..) 也是采用 contextClassLoader 來處理web服務(wù)。

參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容