最近在研究插件化開發(fā),順便就了解了 ClassLoader 這個類加載器刃宵,順藤摸瓜级乍,查到了jvm里面的雙親委派模型,這里就簡單的講一下什么是預(yù)定義類加載器和雙親委派模型竣贪?
學好java基礎(chǔ)军洼,順便學好jvm虛擬機巩螃,對閱讀源碼和插件化開發(fā)很有幫助。
1匕争、預(yù)定義類加載器
JVM預(yù)定義的三種類型類加載器:
1.啟動(Bootstrap)類加載器:是用本地代碼實現(xiàn)的類裝入器避乏,它負責將
<Java_Runtime_Home>/lib
下面的類庫加載到內(nèi)存中(比如rt.jar
)。由于引導類加載器涉及到虛擬機本地實現(xiàn)細節(jié)甘桑,開發(fā)者無法直接獲取到啟動類加載器的引用拍皮,所以不允許直接通過引用進行操作。2.標準擴展(Extension)類加載器:是由 Sun 的
ExtClassLoader(sun.misc.Launcher$ExtClassLoader)
實現(xiàn)的扇住。它負責將< Java_Runtime_Home >/lib/ext
或者由系統(tǒng)變量java.ext.dir
指定位置中的類庫加載到內(nèi)存中春缕。開發(fā)者可以直接使用標準擴展類加載器盗胀。3.系統(tǒng)(System)類加載器:是由 Sun 的
AppClassLoader(sun.misc.Launcher$AppClassLoader)
實現(xiàn)的艘蹋。由于這個類加載器是ClassLoader
中的getSystemClassLoader()
方法的返回值,所以一般也被稱為系統(tǒng)類加載器票灰。它負責將系統(tǒng)類路徑(CLASSPATH
)中指定的類庫加載到內(nèi)存中女阀。開發(fā)者可以直接使用系統(tǒng)類加載器,如果應(yīng)用程序中沒有自定義過自己的類加載器屑迂,一般情況下這個就是程序中默認的類加載器浸策。
除了以上列舉的三種類加載器,還有一種比較特殊的類型 — 線程上下文類加載器
惹盼。
應(yīng)用程序由這三種類加載器互相配合進行加載的庸汗,如果有必須,還可以加入自己定義的類加載器手报。這些類加載器之間的關(guān)系一般如下圖:
2蚯舱、雙親委派模型(雙親委派機制)
雙親委派模型要求除了頂層的啟動類加載器之外,其余的類加載器都應(yīng)當有自己的父類加載器掩蛤。這里的類加載器之間的父子關(guān)系一般不會以繼承的關(guān)系來實現(xiàn)枉昏,而是使用組合關(guān)系來復(fù)用父加載器的代碼。
雙親委派模型的式作過程是:如果一個類加載器收到了類加載的請求揍鸟,它首先不會自己去嘗試加載這個類兄裂,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此阳藻,因此所有的加載請求最終都應(yīng)該傳送到頂層的啟動類加載器中晰奖,只有當父加載器反饋自己無法完全這個加載請求時,子加載器才會嘗試自己去加載腥泥。
3匾南、雙親委派模型的破壞
- 1、第1次“被破壞”
??雙親委派模型的第一次“被破壞”其實發(fā)生在雙親委派模型出現(xiàn)之前--即JDK1.2發(fā)布之前道川。由于雙親委派模型是在JDK1.2之后才被引入的午衰,而類加載器和抽象類 java.lang.ClassLoader
則是JDK1.0時候就已經(jīng)存在立宜,面對已經(jīng)存在 的用戶自定義類加載器的實現(xiàn)代碼,Java設(shè)計者引入雙親委派模型時不得不做出一些妥協(xié)臊岸。為了向前兼容橙数,JDK1.2之后的 java.lang.ClassLoader
添加了一個新的 proceted
方法 findClass()
,在此之前帅戒,用戶去繼承 java.lang.ClassLoader
的唯一目的就是重寫 loadClass()
方法灯帮,因為虛擬在進行類加載的時候會調(diào)用加載器的私有方法 loadClassInternal()
,而這個方法的唯一邏輯就是去調(diào)用自己的 loadClass()
逻住。JDK1.2之后已不再提倡用戶再去覆蓋 loadClass()
方法钟哥,應(yīng)當把自己的類加載邏輯寫到 findClass()
方法中,在 loadClass()
方法的邏輯里瞎访,如果父類加載器加載失敗腻贰,則會調(diào)用自己的findClass()方法來完成加載,這樣就可以保證新寫出來的類加載器是符合雙親委派模型的扒秸。
- 2播演、第2次“被破壞”
??雙親委派模型的第二次“被破壞”是這個模型自身的缺陷所導致的,雙親委派模型很好地解決了各個類加載器的基礎(chǔ)類統(tǒng)一問題(越基礎(chǔ)的類由越上層的加載器進行加載)伴奥,基礎(chǔ)類之所以被稱為“基礎(chǔ)”写烤,是因為它們總是作為被調(diào)用代碼調(diào)用的API。但是拾徙,如果基礎(chǔ)類又要調(diào)用用戶的代碼洲炊,那該怎么辦呢。
??這并非是不可能的事情尼啡,一個典型的例子便是JNDI服務(wù)暂衡,它的代碼由啟動類加載器去加載(在JDK1.3時放進 rt.jar
),但JNDI的目的就是對資源進行集中管理和查找玄叠,它需要調(diào)用獨立廠商實現(xiàn)部部署在應(yīng)用程序的classpath下的JNDI接口提供者 (SPI, Service Provider Interface)
的代碼古徒,但啟動類加載器不可能“認識”之些代碼,該怎么辦读恃?
??為了解決這個困境隧膘,Java設(shè)計團隊只好引入了一個不太優(yōu)雅的設(shè)計:線程上下文件類加載器(Thread Context ClassLoader
)。這個類加載器可以通過 java.lang.Thread
類的 setContextClassLoader()
方法進行設(shè)置寺惫,如果創(chuàng)建線程時還未設(shè)置疹吃,它將會從父線程中繼承一個;如果在應(yīng)用程序的全局范圍內(nèi)都沒有設(shè)置過西雀,那么這個類加載器默認就是應(yīng)用程序類加載器萨驶。了有線程上下文類加載器,JNDI服務(wù)使用這個線程上下文類加載器去加載所需要的SPI代碼艇肴,也就是父類加載器請求子類加載器去完成類加載動作腔呜,這種行為實際上就是打通了雙親委派模型的層次結(jié)構(gòu)來逆向使用類加載器叁温,已經(jīng)違背了雙親委派模型,但這也是無可奈何的事情核畴。Java中所有涉及SPI的加載動作基本上都采用這種方式膝但,例如JNDI,JDBC,JCE,JAXB和JBI等。
- 3谤草、第3次“被破壞”
??雙親委派模型的第三次“被破壞”是由于用戶對程序的動態(tài)性的追求導致的跟束,例如OSGi的出現(xiàn)。在OSGi環(huán)境下丑孩,類加載器不再是雙親委派模型中的樹狀結(jié)構(gòu)冀宴,而是進一步發(fā)展為網(wǎng)狀結(jié)構(gòu)。
4温学、幾點思考
- 1略贮、Bootstrap類
Java虛擬機的第一個類加載器是Bootstrap,這個加載器很特殊枫浙,它不是Java類刨肃,因此它不需要被別人加載,它嵌套在Java虛擬機內(nèi)核里面箩帚,也就是JVM啟動的時候Bootstrap就已經(jīng)啟動,它是用C++寫的二進制代碼(不是字節(jié)碼)黄痪,它可以去加載別的類紧帕。
這也是我們在測試時為什么發(fā)現(xiàn)System.class.getClassLoader()結(jié)果為null的原因,這并不表示System這個類沒有類加載器桅打,而是它的加載器比較特殊是嗜,是BootstrapClassLoader,由于它不是Java類挺尾,因此獲得它的引用肯定返回null鹅搪。
-
2、委托機制具體含義
當Java虛擬機要加載一個類時遭铺,到底派出哪個類加載器去加載呢丽柿?首先當前線程的類加載器去加載線程中的第一個類(假設(shè)為類A)。
注:當前線程的類加載器可以通過Thread類的getContextClassLoader()獲得魂挂,也可以通過setContextClassLoader()自己設(shè)置類加載器甫题。如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B涂召。
還可以直接調(diào)用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類坠非。
-
3、委托機制的意義 — 防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼
比如兩個類A和類B都要加載System類:如果不用委托而是自己加載自己的果正,那么類A就會加載一份System字節(jié)碼炎码,然后類B又會加載一份System字節(jié)碼盟迟,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機制潦闲,會遞歸的向父類查找队萤,也就是首選用Bootstrap嘗試加載,如果找不到再向下矫钓。這里的System就能在Bootstrap中找到然后加載要尔,如果此時類B也要加載System,也從Bootstrap開始新娜,此時Bootstrap發(fā)現(xiàn)已經(jīng)加載過了System那么直接返回內(nèi)存中的System即可而不需要重新加載赵辕,這樣內(nèi)存中就只有一份System的字節(jié)碼了。
5概龄、一道面試題:能不能自己寫個類叫java.lang.System还惠?
答案: 通常不可以,但可以采取另類方法達到這個需求私杜。
解釋: 為了不讓我們寫System類蚕键,類加載采用委托機制,這樣可以保證爸爸們優(yōu)先衰粹,爸爸們能找到的類锣光,兒子就沒有機會加載。而System類是Bootstrap加載器加載的铝耻,就算自己重寫糠惫,也總是使用Java系統(tǒng)提供的System失暂,自己寫的System類根本沒有機會得到加載吮成。
但是蓝仲,我們可以自己定義一個類加載器來達到這個目的,為了避免雙親委托機制泡态,這個類加載器也必須是特殊的搂漠。由于系統(tǒng)自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個特殊的目錄某弦,那么系統(tǒng)的加載器就無法加載桐汤,也就是最終還是由我們自己的加載器加載。
參考文章: