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

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

通過圖2,我們可以看到Bootstrap Class Loader是JVM中的祖先Class Loader。它是JDK中唯一一個由C++所寫的Class Loader,它負責加載JDK的核心類庫(rt.jar)以及另外兩個由Java所寫的Class Loader(Ext Class Loader和App Class Loader)。之后就功成身退,轉由Ext Class Loader和App Class Loader加載所有應用中用到的類。
一般我們的應用都是通過設置CLASSPATH,最終由App Class Loader來加載。根據Class Loader的繼承關系,我們應用中的類可以訪問JDK的核心類庫。反之則會出錯。
我們再來看看WebLogic中Class Loader的層次關系(如圖4)。如果需要,大家可以參考一下WebLogic中WAR和EAR的文件結構(如圖3)


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