眾所周知,所有的Java class文件都是由JVM(虛擬機(jī))加載并執(zhí)行的。深入理解JVM對(duì)于我們提高Java技術(shù)和解決Java問題都有非常大的幫助。
JVM內(nèi)部主要包括內(nèi)存管理和Class Loader(類加載器)兩個(gè)部分。熟悉了內(nèi)存管理,我們就會(huì)清楚程序在內(nèi)存中是怎么分配和執(zhí)行的,就能解決所有和對(duì)象相關(guān)的問題(比如Memory Leak)。理解了Class Loader,就能解決所有類找不到(比如遇到NoClassDefFoundError或ClassNotFoundException)或配置文件找不到問題。
這次我們只討論JVM的Class Loader,下次再討論JVM的內(nèi)存管理。
Class Loader的主要作用就是負(fù)責(zé)查找類并將其加載到內(nèi)存中。有趣的是,Java中的Class Loader也是由Java所寫,就和普通的class一樣。這就產(chǎn)生了一個(gè)是雞生蛋還是蛋生雞的問題,到底第一個(gè)class由誰來加載呢?我們稍后會(huì)來討論這個(gè)問題。
先來看一下Class Loader所具有的特點(diǎn)。
1. 繼承關(guān)系
雖然Class Loader也是一個(gè)Java class,但這里的繼承不是指定義class時(shí)使用的extends關(guān)鍵字來實(shí)現(xiàn)的繼承,而是指由屬性來維持的繼承關(guān)系。即通過Class Loader的構(gòu)造方法或其它方法顯式的設(shè)置一個(gè)父Class Loader。
2. 代理關(guān)系
每一個(gè)Class Loader在接到請(qǐng)求去加載一個(gè)類之前(默認(rèn),訪問一個(gè)類的時(shí)候,就會(huì)由加載當(dāng)前類的Class Loader去加載被訪問的類),它會(huì)首先請(qǐng)求它的父Class Loader來嘗試加載,依次往上,如果父Class Loader加載成功,則直接返回,子Class Loader不再查找。
否則依次往下查找并加載。如果直到被請(qǐng)求的Class Loader也沒有找到要加載的類,則會(huì)出現(xiàn)NoClassDefFoundError或ClassNotFoundException
當(dāng)然如果被請(qǐng)求的類已經(jīng)加載到了內(nèi)存中,就不會(huì)觸發(fā)這個(gè)查找過程了,而是直接返回已經(jīng)加載的類。
我們來看一個(gè)例子,假設(shè)有圖1中的Class Loader層次:

如果我們請(qǐng)求Class Loader E去加載Test.class,首先它會(huì)請(qǐng)求父Class Loader D去嘗試加載。同樣Class Loader D會(huì)先請(qǐng)求它的父Class Loader C去嘗試加載Test.class。當(dāng)然這里Class Loader C找不到Test.class,于是轉(zhuǎn)回由Class Loader D去加載。最終Class Loader D成功找到了D:"Test.class,并將其加載到內(nèi)存中。
同樣,如果我們請(qǐng)求Class Loader F去加載Test3.class。Class Loader C和Class Loader D在各自的搜索范圍內(nèi)都找不到Test3.class。最終會(huì)由Class Loader F自己加載F:"Test3.class到內(nèi)存中。
如果我們請(qǐng)求Class Loader D去加載Test3.class,最終就會(huì)出現(xiàn)NoClassDefFoundError或ClassNotFoundException
3. 同一繼承鏈可見性
在同一個(gè)Class Loader對(duì)象的繼承鏈中,下面被加載的類可以訪問上面被加載的類,反之則不可以。
同樣以圖1為例,Test4.class可以訪問到D:"Test.class和C:"Test2.class
而如果D:"Test.class或C:"Test2.class嘗試訪問Test4.class,就會(huì)出現(xiàn)NoClassDefFoundError或ClassNotFoundException
4. 多個(gè)繼承鏈不可見性
多個(gè)繼承鏈之間彼此看不到對(duì)方,不能相互訪問。
還以圖1為例,如果Test4.class訪問Test3.class,或反過來Test3.class訪問Test4.class,都會(huì)引起NoClassDefFoundError或ClassNotFoundException
理解了Class Loader所具有的特點(diǎn),我們來看看JDK中都預(yù)置了哪些Class Loader。也是JVM啟動(dòng)時(shí)默認(rèn)創(chuàng)建的Class Loader。如圖2

通過圖2,我們可以看到Bootstrap Class Loader是JVM中的祖先Class Loader。它是JDK中唯一一個(gè)由C++所寫的Class Loader,它負(fù)責(zé)加載JDK的核心類庫(rt.jar)以及另外兩個(gè)由Java所寫的Class Loader(Ext Class Loader和App Class Loader)。之后就功成身退,轉(zhuǎn)由Ext Class Loader和App Class Loader加載所有應(yīng)用中用到的類。
一般我們的應(yīng)用都是通過設(shè)置CLASSPATH,最終由App Class Loader來加載。根據(jù)Class Loader的繼承關(guān)系,我們應(yīng)用中的類可以訪問JDK的核心類庫。反之則會(huì)出錯(cuò)。
我們?cè)賮砜纯?/span>WebLogic中Class Loader的層次關(guān)系(如圖4)。如果需要,大家可以參考一下WebLogic中WAR和EAR的文件結(jié)構(gòu)(如圖3)


WLS中自定義了很多新的Class Loader,當(dāng)然他們的祖先Class Loader都是JDK中的App (or System) Class Loader。我們來看一下每個(gè)Class Loader的職責(zé)。
1. JDK App (or System) Class Loader
l 負(fù)責(zé)加載WLS啟動(dòng)腳本中CLASSPATH中設(shè)置的類
l 所有的類都會(huì)最先由它嘗試加載
l 因?yàn)?/span>CLASSPATH的值在運(yùn)行期不允許修改,所以由該Class Loader加載的類在運(yùn)行期不能被動(dòng)態(tài)卸載(替換)
2. EJB Class Loader (1)
l 負(fù)責(zé)加載單獨(dú)的EJB jar里的類。
l 不同的EJB jar文件會(huì)被不同實(shí)例的Class Loader加載,因此EJB jar彼此之間互相看不到對(duì)方
3. WAR Class Loader (1)
l 負(fù)責(zé)加載單獨(dú)的WAR里的類
l 不同的WAR文件會(huì)被不同實(shí)例的Class Loader加載,因此WAR彼此之間互相看不到對(duì)方
4. EAR Class Loader
l 負(fù)責(zé)加載EAR里面的APP-INF下的類
l 不同的EAR文件會(huì)被不同實(shí)例的EAR Class Loader加載,因此EAR彼此之間互相看不到對(duì)方
l 它下面有一個(gè)EJB Class Loader (2) 實(shí)例,負(fù)責(zé)加載EAR里面所有的EJB jar。因此,EAR中的EJB彼此之間可以看到對(duì)方
l EJB Class Loader (2) 下有多個(gè)WAR Class Loader (2) 實(shí)例。每個(gè)實(shí)例負(fù)責(zé)加載EAR里面的一個(gè)WAR。所以,EAR中的WAR彼此之間看不到對(duì)方
l 根據(jù)繼承鏈規(guī)則,WAR可以看到所有的EJB及APP-INF下的所有類。 EJB可以看到APP-INF下的所有類,但反之則不可以
Class Loader雖然稱為類加載器,但并不意味著只能用來加載Class,我們還可以利用它來查找圖片和配置文件等資源。比如,我們經(jīng)常使用getClass().getResourceAsStream(name)來查找配置文件。同樣,查找其它資源文件的方式和上面一樣,也會(huì)先請(qǐng)求父Class Loader來負(fù)責(zé)查找。
這里,我們只簡(jiǎn)單介紹了Class Loader對(duì)于類的查找,而關(guān)于Class Loader的具體加載、校驗(yàn)和初始化的過程,感興趣的朋友可以參考《深入Java虛擬機(jī)》