什么是ClassLoader
ClassLoader是一個抽象類,我們用它的實(shí)例對象來裝載類,它負(fù)責(zé)將 Java 字節(jié)碼裝載到 JVM 中, 并使其成為 JVM 一部分。 JVM 的類動態(tài)裝載技術(shù)能夠在運(yùn)行時刻動態(tài)地加載或者替換系統(tǒng)的某些功能模塊,而不影響系統(tǒng)其他功能模塊的正常運(yùn)行。一般是通過類名讀入一個class文件來裝載這個類,(其它加載形式暫時沒有研究過)。
JAVA一般用三個class loader完成類的裝載任務(wù)(自己定義的class loader出外),它們是Bootstrap Loader、ExtClassLoader和AppClassLoader,最后一個也被SUN稱為system class loader。它們的組織方式是:先由Bootstrap Loader生成ExtClassLoader,并設(shè)置ExtClassLoader的parent為null(其實(shí)準(zhǔn)確得說其parent是Bootstrap Loader,但因?yàn)锽ootstrap Loader由C++寫成,并不是邏輯意義上的JAVA類,所以只能為null);然后Bootstrap Loader生成AppclassLoader,設(shè)置其parent為ExtClassLoader。
類的裝載采用的是代理模式:裝載一個類時先由AppClassLoader或自己定義的class loader請求其parent裝載,parent再請求它的parent,這樣到了頂級,也就是Bootstrap Loader,如果parent looder找到了要裝載的類,就由parent loader裝載,否則就由它的"下級"去干這件事。這樣就一級級委托知道把類找到裝載完畢。要說明的是ExtClassLoader和 AppClassLoader都是URLClassLoader的子類,而URLClassLoader可由一個URL對象確定它尋找類的路徑,我們可以通過下面的方法發(fā)現(xiàn)前述的三種class loader的搜尋路徑:
- System.out.println(System.getProperty("sun.boot.class.path"))
- System.out.println(System.getProperty("java.class.path"))
- System.out.println(System.getProperty("java.ext.dirs"))
ClassLoader裝載過程
類裝載就是尋找一個類或是一個接口的字節(jié)碼文件并通過解析該字節(jié)碼來構(gòu)造代表這個類或是這個接口的 class 對象的過程。在 Java 中,類裝載器把一個類裝入 Java 虛擬機(jī)中,要經(jīng)過三個步驟來完成:裝載、鏈接和初始化,其中鏈接又可以分成校驗(yàn)、準(zhǔn)備和解析三步,除了解析外,其它步驟是嚴(yán)格按照順序完成的,各個步驟的主要工作如下:
1. 裝載:查找和導(dǎo)入類或接口的字節(jié)碼;
2. 鏈接:執(zhí)行下面的校驗(yàn)、準(zhǔn)備和解析步驟,其中解析步驟是可以選擇的;
l 校驗(yàn):檢查導(dǎo)入類或接口的二進(jìn)制數(shù)據(jù)的正確性;
l 準(zhǔn)備:給類的靜態(tài)變量分配并初始化存儲空間;
l 解析:將符號引用轉(zhuǎn)成直接引用;
3. 初始化:激活類的靜態(tài)變量的初始化 Java 代碼和靜態(tài) Java 代碼塊。
裝載的實(shí)現(xiàn)
JVM 中類的裝載是由 ClassLoader 和它的子類來實(shí)現(xiàn)的。 Java ClassLoader 是一個重要的 Java 運(yùn)行時系統(tǒng)組件,它負(fù)責(zé)在運(yùn)行時查找和裝入 Java 字節(jié)碼。
在 Java 中, ClassLoader 是一個抽象類,它在包 java.lang 中。可以這樣說,只要了解了 ClassLoader 中的一些重要的方法,再結(jié)合上面所介紹的 JVM 中類裝載的具體的過程,對動態(tài)裝載類這項(xiàng)技術(shù)就有了一個比較大概的掌握,這些重要的方法包括以下幾個:
1. loadCass 方法: loadClass(String name ,boolean resolve) 其中 name 參數(shù)指定了 JVM 需要的類的名稱 , 該名稱以類的全限定名表示,如 Java.lang.Object ; resolve 參數(shù)告訴方法是否需要解析類,在初始化類之前,應(yīng)考慮類解析,并不是所有的類都需要解析,如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。這個方法是 ClassLoader 的入口點(diǎn)。
2. defineClass 方法 這個方法接受類文件的字節(jié)數(shù)組并把它轉(zhuǎn)換成 Class 對象。字節(jié)數(shù)組可以是從本地文件系統(tǒng)或網(wǎng)絡(luò)裝入的數(shù)據(jù)。它把字節(jié)碼分析成運(yùn)行時數(shù)據(jù)結(jié)構(gòu)、校驗(yàn)有效性等等。
3. findSystemClass 方法 findSystemClass 方法從本地文件系統(tǒng)裝入 Java 字節(jié)碼。它在本地文件系統(tǒng)中尋找類文件,如果存在,就使用 defineClass 將字節(jié)數(shù)組轉(zhuǎn)換成 Class 對象。當(dāng)運(yùn)行 Java 應(yīng)用程序時 , 這是 JVM 正常裝入類的缺省機(jī)制。
4. resolveClass 方法 resolveClass(Class c) 方法解析裝入的類,如果該類已經(jīng)被解析過那么將不做處理。當(dāng)調(diào)用 loadClass 方法時 , 通過它的 resolve 參數(shù)決定是否要進(jìn)行解析。
5. findLoadedClass 方法 當(dāng)調(diào)用 loadClass 方法裝入類時 , 調(diào)用 findLoadedClass 方法來查看 ClassLoader 是否已裝入這個類 , 如果已裝入 , 那么返回 Class 對象 , 否則返回 NULL 。如果強(qiáng)行裝載已存在的類 , 將會拋出鏈接錯誤。
java.lang.Class類
某 個類的所有實(shí)例內(nèi)部都有一個引用,指向該類對應(yīng)的Class的實(shí)例的位置,每個java類對應(yīng)的Class實(shí)例可以當(dāng)作是類在內(nèi)存中的代理人。所以當(dāng)要獲 得類的信息(如有哪些類變量,有哪些方法)時,都可以讓類對應(yīng)的Class的實(shí)例代勞.java的Reflection機(jī)制就大量的使用這種方法來實(shí)現(xiàn)。 但是Class類無法手工實(shí)例化,當(dāng)載入任意類的時候自動創(chuàng)建一個該類對應(yīng)的Class的實(shí)例。每個java類都是由某個classLoader (ClassLoader的實(shí)例)來載入的,因此Class類別的實(shí)例中都會他的ClassLoader的實(shí)例的引用。可以通過 getClass.getClassLoader()得到CLassLoader的實(shí)例。
java動態(tài)載入class的兩種方式:
1)implicit隱式,即利用實(shí)例化才載入的特性來動態(tài)載入class
2)explicit顯式方式,又分兩種方式:
a)java.lang.Class的forName()方法
b)java.lang.ClassLoader的loadClass()方法
各種java類由哪些classLoader加載?
1)java類可以通過實(shí)例.getClass.getClassLoader()得到
2)接口由AppClassLoader(SystemClassLoader)載入 ,SystemClassLoader:可以由ClassLoader.getSystemClassLoader()獲得實(shí)例
3)ClassLoader類由bootstrap loader載入
ClassLoader hierachy:
1)jvm初始化產(chǎn)生bootstrap loader。并設(shè)定它的父ClassLoader為null
2)bootstrap loader建立AppClassLoader,載入 運(yùn)行java.exe時 的-cp或-classpath中的類(每個運(yùn)行中的線程都有一個成員contextClassLoader,用來在運(yùn)行時動態(tài)地載入其它類系統(tǒng)默認(rèn)的 contextClassLoader是systemClassLoader)

Java 類裝載的代理結(jié)構(gòu)
根 (Bootstrap) 裝載器:該裝載器沒有父裝載器,它是 JVM 實(shí)現(xiàn)的一部分,從 sun.boot.class.path 裝載運(yùn)行時庫的核心代碼。
擴(kuò)展 (Extension) 裝載器:繼承的父裝載器為根裝載器,不像根裝載器可能與運(yùn)行時的操作系統(tǒng)有關(guān),這個類裝載器是用純 Java 代碼實(shí)現(xiàn)的,它從 java.ext.dirs ( 擴(kuò)展目錄 ) 中裝載代碼。
系統(tǒng) (System or Application) 裝載器:裝載器為擴(kuò)展裝載器,我們都知道在安裝 JDK 的時候要設(shè)置環(huán)境變量 (CLASSPATH ) ,這個類裝載器就是從 java.class.path(CLASSPATH 環(huán)境變量 ) 中裝載代碼的,它也是用純 Java 代碼實(shí)現(xiàn)的,同時還是用戶自定義類裝載器的缺省父裝載器。
小應(yīng)用程序 (Applet) 裝載器:父裝載器為系統(tǒng)裝載器,它從用戶指定的網(wǎng)絡(luò)上的特定目錄裝載小應(yīng)用程序代碼。
java中的代理結(jié)構(gòu)是自上而下查找類的,這與很多web裝載器不同。
tomcat中的實(shí)現(xiàn)的子ClassLoader的結(jié)構(gòu)
Tomcat Server在啟動的時候?qū)?gòu)造一個ClassLoader樹,以保證模塊的類庫是私有的
Tomcat Server的ClassLoader結(jié)構(gòu)如下:
---------------------------+
| Bootstrap |
| | |
| System |
| | |
| Common |
| / \ |
| Catalina Shared |
| / \ |
| WebApp1 WebApp2 |
---------------------------+
其中:
- Bootstrap - 載入JVM自帶的類和$JAVA_HOME/jre/lib/ext/*.jar
- System - 載入$CLASSPATH/*.class
- Common - 載入$CATALINA_HOME/common/...,它們對TOMCAT和所有的WEB APP都可見
- Catalina - 載入$CATALINA_HOME/server/...,它們僅對TOMCAT可見,對所有的WEB APP都不可見
- Shared - 載入$CATALINA_HOME/shared/...,它們僅對所有WEB APP可見,對TOMCAT不可見(也不必見)
- WebApp? - 載入ContextBase?/WEB-INF/...,它們僅對該WEB APP可見
ClassLoader被組織成樹形,一般的工作原理是:
1) 線程需要用到某個類,于是contextClassLoader被請求來載入該類
2) contextClassLoader請求它的父ClassLoader來完成該載入請求
3) 如果父ClassLoader無法載入類,則contextClassLoader試圖自己來載入
注意:WebApp?ClassLoader的工作原理和上述有少許不同:
它先試圖自己載入類(在ContextBase?/WEB-INF/...中載入類),如果無法載入,再請求父ClassLoader完成