最近在研究插件化開(kāi)發(fā),順便就了解了 ClassLoader 這個(gè)類加載器,順藤摸瓜,查到了jvm里面的雙親委派模型,這里就簡(jiǎn)單的講一下什么是預(yù)定義類加載器和雙親委派模型?
學(xué)好java基礎(chǔ),順便學(xué)好jvm虛擬機(jī),對(duì)閱讀源碼和插件化開(kāi)發(fā)很有幫助。
1、預(yù)定義類加載器
JVM預(yù)定義的三種類型類加載器:
1.啟動(dòng)(Bootstrap)類加載器:是用本地代碼實(shí)現(xiàn)的類裝入器,它負(fù)責(zé)將
<Java_Runtime_Home>/lib下面的類庫(kù)加載到內(nèi)存中(比如rt.jar)。由于引導(dǎo)類加載器涉及到虛擬機(jī)本地實(shí)現(xiàn)細(xì)節(jié),開(kāi)發(fā)者無(wú)法直接獲取到啟動(dòng)類加載器的引用,所以不允許直接通過(guò)引用進(jìn)行操作。2.標(biāo)準(zhǔn)擴(kuò)展(Extension)類加載器:是由 Sun 的
ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實(shí)現(xiàn)的。它負(fù)責(zé)將< Java_Runtime_Home >/lib/ext或者由系統(tǒng)變量java.ext.dir指定位置中的類庫(kù)加載到內(nèi)存中。開(kāi)發(fā)者可以直接使用標(biāo)準(zhǔn)擴(kuò)展類加載器。3.系統(tǒng)(System)類加載器:是由 Sun 的
AppClassLoader(sun.misc.Launcher$AppClassLoader)實(shí)現(xiàn)的。由于這個(gè)類加載器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也被稱為系統(tǒng)類加載器。它負(fù)責(zé)將系統(tǒng)類路徑(CLASSPATH)中指定的類庫(kù)加載到內(nèi)存中。開(kāi)發(fā)者可以直接使用系統(tǒng)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
除了以上列舉的三種類加載器,還有一種比較特殊的類型 — 線程上下文類加載器。
應(yīng)用程序由這三種類加載器互相配合進(jìn)行加載的,如果有必須,還可以加入自己定義的類加載器。這些類加載器之間的關(guān)系一般如下圖:

2、雙親委派模型(雙親委派機(jī)制)
雙親委派模型要求除了頂層的啟動(dòng)類加載器之外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系來(lái)實(shí)現(xiàn),而是使用組合關(guān)系來(lái)復(fù)用父加載器的代碼。
雙親委派模型的式作過(guò)程是:如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完全這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。
3、雙親委派模型的破壞
- 1、第1次“被破壞”
??雙親委派模型的第一次“被破壞”其實(shí)發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前。由于雙親委派模型是在JDK1.2之后才被引入的,而類加載器和抽象類 java.lang.ClassLoader 則是JDK1.0時(shí)候就已經(jīng)存在,面對(duì)已經(jīng)存在 的用戶自定義類加載器的實(shí)現(xiàn)代碼,Java設(shè)計(jì)者引入雙親委派模型時(shí)不得不做出一些妥協(xié)。為了向前兼容,JDK1.2之后的 java.lang.ClassLoader 添加了一個(gè)新的 proceted 方法 findClass() ,在此之前,用戶去繼承 java.lang.ClassLoader 的唯一目的就是重寫(xiě) loadClass() 方法,因?yàn)樘摂M在進(jìn)行類加載的時(shí)候會(huì)調(diào)用加載器的私有方法 loadClassInternal(),而這個(gè)方法的唯一邏輯就是去調(diào)用自己的 loadClass() 。JDK1.2之后已不再提倡用戶再去覆蓋 loadClass() 方法,應(yīng)當(dāng)把自己的類加載邏輯寫(xiě)到 findClass() 方法中,在 loadClass() 方法的邏輯里,如果父類加載器加載失敗,則會(huì)調(diào)用自己的findClass()方法來(lái)完成加載,這樣就可以保證新寫(xiě)出來(lái)的類加載器是符合雙親委派模型的。
- 2、第2次“被破壞”
??雙親委派模型的第二次“被破壞”是這個(gè)模型自身的缺陷所導(dǎo)致的,雙親委派模型很好地解決了各個(gè)類加載器的基礎(chǔ)類統(tǒng)一問(wèn)題(越基礎(chǔ)的類由越上層的加載器進(jìn)行加載),基礎(chǔ)類之所以被稱為“基礎(chǔ)”,是因?yàn)樗鼈兛偸亲鳛楸徽{(diào)用代碼調(diào)用的API。但是,如果基礎(chǔ)類又要調(diào)用用戶的代碼,那該怎么辦呢。
??這并非是不可能的事情,一個(gè)典型的例子便是JNDI服務(wù),它的代碼由啟動(dòng)類加載器去加載(在JDK1.3時(shí)放進(jìn) rt.jar ),但JNDI的目的就是對(duì)資源進(jìn)行集中管理和查找,它需要調(diào)用獨(dú)立廠商實(shí)現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者 (SPI, Service Provider Interface) 的代碼,但啟動(dòng)類加載器不可能“認(rèn)識(shí)”之些代碼,該怎么辦?
??為了解決這個(gè)困境,Java設(shè)計(jì)團(tuán)隊(duì)只好引入了一個(gè)不太優(yōu)雅的設(shè)計(jì):線程上下文件類加載器(Thread Context ClassLoader)。這個(gè)類加載器可以通過(guò) java.lang.Thread 類的 setContextClassLoader() 方法進(jìn)行設(shè)置,如果創(chuàng)建線程時(shí)還未設(shè)置,它將會(huì)從父線程中繼承一個(gè);如果在應(yīng)用程序的全局范圍內(nèi)都沒(méi)有設(shè)置過(guò),那么這個(gè)類加載器默認(rèn)就是應(yīng)用程序類加載器。了有線程上下文類加載器,JNDI服務(wù)使用這個(gè)線程上下文類加載器去加載所需要的SPI代碼,也就是父類加載器請(qǐng)求子類加載器去完成類加載動(dòng)作,這種行為實(shí)際上就是打通了雙親委派模型的層次結(jié)構(gòu)來(lái)逆向使用類加載器,已經(jīng)違背了雙親委派模型,但這也是無(wú)可奈何的事情。Java中所有涉及SPI的加載動(dòng)作基本上都采用這種方式,例如JNDI,JDBC,JCE,JAXB和JBI等。
- 3、第3次“被破壞”
??雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的,例如OSGi的出現(xiàn)。在OSGi環(huán)境下,類加載器不再是雙親委派模型中的樹(shù)狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)。
4、幾點(diǎn)思考
- 1、Bootstrap類
Java虛擬機(jī)的第一個(gè)類加載器是Bootstrap,這個(gè)加載器很特殊,它不是Java類,因此它不需要被別人加載,它嵌套在Java虛擬機(jī)內(nèi)核里面,也就是JVM啟動(dòng)的時(shí)候Bootstrap就已經(jīng)啟動(dòng),它是用C++寫(xiě)的二進(jìn)制代碼(不是字節(jié)碼),它可以去加載別的類。
這也是我們?cè)跍y(cè)試時(shí)為什么發(fā)現(xiàn)System.class.getClassLoader()結(jié)果為null的原因,這并不表示System這個(gè)類沒(méi)有類加載器,而是它的加載器比較特殊,是BootstrapClassLoader,由于它不是Java類,因此獲得它的引用肯定返回null。
-
2、委托機(jī)制具體含義
當(dāng)Java虛擬機(jī)要加載一個(gè)類時(shí),到底派出哪個(gè)類加載器去加載呢?首先當(dāng)前線程的類加載器去加載線程中的第一個(gè)類(假設(shè)為類A)。
注:當(dāng)前線程的類加載器可以通過(guò)Thread類的getContextClassLoader()獲得,也可以通過(guò)setContextClassLoader()自己設(shè)置類加載器。如果類A中引用了類B,Java虛擬機(jī)將使用加載類A的類加載器去加載類B。
還可以直接調(diào)用ClassLoader.loadClass()方法來(lái)指定某個(gè)類加載器去加載某個(gè)類。
-
3、委托機(jī)制的意義 — 防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
比如兩個(gè)類A和類B都要加載System類:如果不用委托而是自己加載自己的,那么類A就會(huì)加載一份System字節(jié)碼,然后類B又會(huì)加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機(jī)制,會(huì)遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時(shí)類B也要加載System,也從Bootstrap開(kāi)始,此時(shí)Bootstrap發(fā)現(xiàn)已經(jīng)加載過(guò)了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了。
5、一道面試題:能不能自己寫(xiě)個(gè)類叫java.lang.System?
答案: 通常不可以,但可以采取另類方法達(dá)到這個(gè)需求。
解釋: 為了不讓我們寫(xiě)System類,類加載采用委托機(jī)制,這樣可以保證爸爸們優(yōu)先,爸爸們能找到的類,兒子就沒(méi)有機(jī)會(huì)加載。而System類是Bootstrap加載器加載的,就算自己重寫(xiě),也總是使用Java系統(tǒng)提供的System,自己寫(xiě)的System類根本沒(méi)有機(jī)會(huì)得到加載。
但是,我們可以自己定義一個(gè)類加載器來(lái)達(dá)到這個(gè)目的,為了避免雙親委托機(jī)制,這個(gè)類加載器也必須是特殊的。由于系統(tǒng)自帶的三個(gè)類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個(gè)特殊的目錄,那么系統(tǒng)的加載器就無(wú)法加載,也就是最終還是由我們自己的加載器加載。
參考文章:
深入理解Java虛擬機(jī)筆記---雙親委派模型
關(guān)于Java類加載雙親委派機(jī)制的思考(附一道面試題)