網(wǎng)站建設(shè)網(wǎng)站建設(shè)網(wǎng)站運(yùn)營(yíng)與維護(hù)
優(yōu)質(zhì)博文:IT-BLOG-CN
虛擬機(jī)設(shè)計(jì)團(tuán)隊(duì)把類加載階段中的 “通過(guò)一個(gè)類的全限定名來(lái)獲取描述此類的二進(jìn)制字節(jié)流” 這個(gè)動(dòng)作放到 Java虛擬機(jī)外部去實(shí)現(xiàn),以便應(yīng)用程序自己決定如何去獲取所需要的類。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊稱為“類加載器”。
從Java
虛擬機(jī)的角度上,只存在兩種不同的類加載器:一種是啟動(dòng)類加載器(Bootstrap ClassLoader
),這個(gè)類加載器使用C++
語(yǔ)言實(shí)現(xiàn),是虛擬機(jī)自身的一部分;另外一種就是其它所有的類加載器,這些類加載器都由Java
語(yǔ)言實(shí)現(xiàn),獨(dú)立于虛擬機(jī)外部,并且全部繼承自java.lang.ClassLoader
。
從Java
開發(fā)人員的角度看,類加載器還可以劃分得更細(xì)一些,如下:
【1】啟動(dòng)類加載器Bootstrap ClassLoader
: 這個(gè)類加載器負(fù)責(zé)將放置在<JAVA_HOME>\lib
目錄中的,或者被-Xbootclasspath
參數(shù)所指定路徑中的,并且是虛擬機(jī)能識(shí)別的(僅按照文件名識(shí)別,如rt.jar
,名字不符合的類庫(kù)即使放置在lib
目錄中也不會(huì)被加載)類庫(kù)加載到虛擬機(jī)內(nèi)存中。啟動(dòng)類加載器無(wú)法被Java
程序直接使用。
【2】擴(kuò)展類加載器Extension ClassLoader
: 這個(gè)類加載器由sun.misc.Launcher$ExtClassLoader
實(shí)現(xiàn),它負(fù)責(zé)加載<JAVA_HOME>\lib\ext
目錄中的,或者被java.ext.dirs
系統(tǒng)變量所指定的路徑中的所有類庫(kù),開發(fā)者可以直接使用擴(kuò)展類加載器。
【3】應(yīng)用程序類加載器Application ClassLoader
: 這個(gè)類加載器由sum.misc.Launcher.$AppClassLoader
來(lái)實(shí)現(xiàn)。由于這個(gè)類加載器是ClassLoader
中的getSystemClassLoader()
方法的返回值,所以一般也被稱為系統(tǒng)類加載器。它負(fù)責(zé)加載用戶類路徑上所指定的類庫(kù),開發(fā)者可以直接使用這個(gè)類加載器,如果應(yīng)用程序中沒(méi)有自定義過(guò)自己的類加載器,一般情況下這個(gè)就是程序中默認(rèn)的類加載器。
應(yīng)用程序由這三種類加載器互相配合進(jìn)行加載的,如果有必須,還可以加入自己定義的類加載器。這些類加載器之間的關(guān)系一般如下圖:
上圖中展示的類加載器之間的層次關(guān)系,就稱為類加載器的雙親委派模型Parents Delegation Model
。雙親委派模型要求除了頂層的啟動(dòng)類加載器之外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器。這里的類加載器之間的父子關(guān)系一般不會(huì)以繼承的關(guān)系來(lái)實(shí)現(xiàn),而是使用組合Composition
關(guān)系來(lái)復(fù)用父加載器的代碼。
雙親委派模型的工作過(guò)程是(重點(diǎn)):如果一個(gè)類加載器收到了類加載的請(qǐng)求,它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成,每一個(gè)層次的類加載器都是如此,因此所有的加載請(qǐng)求最終都應(yīng)該傳送到頂層的啟動(dòng)類加載器中,只有當(dāng)父加載器反饋?zhàn)约簾o(wú)法完成該加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去加載。
使用雙親委派模型來(lái)組織類加載器的一個(gè)好處就是Java
類因類加載器具備了一種帶有優(yōu)先級(jí)的層次關(guān)系。例如Object
類,他存放在rt.jar
之中,無(wú)論哪一個(gè)類加載器要加載這個(gè)類,最終都是委派給處于模型最頂端的啟動(dòng)類加載器進(jìn)行加載,因此Object
類在程序的各種類加載器環(huán)境中都是同一個(gè)類。雙親委派模型對(duì)于保證Java
程序的穩(wěn)定運(yùn)作很重要,但它的實(shí)現(xiàn)非常簡(jiǎn)單,實(shí)現(xiàn)雙親委派代碼都集中在java.lang.ClassLoader
的loadClass()
方法中,如下,邏輯簡(jiǎn)單清晰,先檢查是否已經(jīng)被加載過(guò),若沒(méi)有加載則調(diào)用父加載器的loadClass()
方法,若父加載器為空則默認(rèn)使用啟動(dòng)類加載器作為父加載器。如果父類加載失敗,拋出ClassNotFoundException
異常后,在調(diào)用自己的findClass()
方法進(jìn)行加載。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{synchronized (getClassLoadingLock(name)) {// 首先,檢查請(qǐng)求的類是否被加載過(guò)Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 如果父類加載器拋出 ClassNotFoundException// 說(shuō)明父類加載器無(wú)法完成加載請(qǐng)求}if (c == null) {// 在父類加載器無(wú)法加載的時(shí)候//在調(diào)用本身的 findClass 方法來(lái)進(jìn)行加載long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}// findClass 直接拋出 ClassNotFoundExceptionprotected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}
}
破壞雙拼委派模型
雙親委派模型并不是一個(gè)強(qiáng)制性的約束模型,而是Java
設(shè)計(jì)者推薦給開發(fā)者的類加載器實(shí)現(xiàn)方式。目前為止,雙親委派的具體邏輯就實(shí)現(xiàn)在loadClass
方法之中。應(yīng)當(dāng)把自己的類加載邏輯寫到findClass()
方法中,在loadClass()
方法的邏輯里如果父類加載失敗,則會(huì)調(diào)用自己的findClass()
方法完成加載,這樣就保證了新寫出來(lái)的類加載器是符合雙親委派規(guī)則。
如果基礎(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
等。
雙親委派模型的第三次“被破壞”是由于用戶對(duì)程序的動(dòng)態(tài)性的追求導(dǎo)致的,例如OSGi
的出現(xiàn)。在OSGi
環(huán)境下,類加載器不再是雙親委派模型中的樹狀結(jié)構(gòu),而是進(jìn)一步發(fā)展為網(wǎng)狀結(jié)構(gòu)。