【正文】Java類加載器( CLassLoader ) 死磕 之4:
神祕的雙親委託機制
本小節目錄
4.1. 每個類加載器都有一個parent父加載器
4.2. 類加載器之間的層次關係
4.3. 類的加載次序
4.4 雙親委託機制原理與沙箱機制
4.5. forName方法和loadClass方法的關係
4.6. 使用組合而不用繼承
4.7. 各種不同的類加載途徑
4.1.每個類加載器都有一個parent父加載器
每個類加載器都有一個parent父加載器,比如加載SystemConfig.class是由AppClassLoader完成,那麼AppClassLoader也有一個父加載器,怎麼樣獲取呢?很簡單,通過getParent方法。
這裏寫了一個公共的函數,來取得一個類的加載器的雙親樹。代碼如下:
/** * 迭代,顯示class loader 和 父加載器 */ public static void showLoaderTree(ClassLoader loader) { while (loader != null) { Logger.info(loader.toString()); loader = loader.getParent(); } }
這樣,就展示了一棵類加載器的雙親樹。
使用上面的函數,演示代碼如下:
private static void loaderTreeDemo() throws ClassNotFoundException
{
String className = "com.crazymakercircle.config.SystemConfig";
Class<?> aClass = Class.forName(className);
ClassLoader aLoader=aClass.getClassLoader();
Logger.info("加載器:"+aLoader.toString());
ClassLoaderUtil.showLoaderTree(aLoader);
}
案例路徑:com.crazymakercircle.classLoaderDemo.base.ParentTreeDemo
案例提示:無編程不創客、無案例不學習。切記,一定要跑案例哦
案例結果如下:
loaderTreeDemo |> 加載器:sun.misc.Launcher$AppClassLoader@18b4aac2 showLoaderTree |> sun.misc.Launcher$AppClassLoader@18b4aac2 showLoaderTree |> sun.misc.Launcher$ExtClassLoader@6fdb1f78
這個說明,當前加載器類型爲AppClassLoader,而AppClassLoader父加載器類是ExtClassLoader。ExtClassLoader的父加載器,又是誰呢? 沒有了打印。
parent爲空表示什麼意思呢?
我們先來梳理一下加載器之間的層次關係。
4.2. 類加載器之間的層次關係
下面展示一下Bootstrap 啓動類加載器、Extention標準擴展類加載器和App應用類加載器三者之間的關係。
大致整理了如下類似的一幅圖片:
每一個加載器看護一塊自己的地盤。 啓動Bootstrap 看護的是核心中的核心地盤。
前面講到,ExtClassLoader的父加載器爲空。而上圖中,ExtClassLoader的父加載器是Bootstrap 啓動類加載器。
實際上,如果一個加載器的parent爲空,其父親加載器就是Bootstrap 啓動類加載器。
如果沒有特別的設置,自定義加載的parent,默認爲App應用加載器。
4.3. 類的加載次序
loadClass 關鍵源代碼,已經在前面有介紹。
下面用一張圖,對於類的加載次序,做進一步的介紹。
一般場景下,加載一個類,是從AppClassLoader開始的。
基本的步驟如下:
(1)AppClassLoader查找資源時,不是首先查看自己的地盤是否有這個字節碼文件,而是直接委託給父加載器ExtClassLoader。當然,這裏有一個假定,就是在AppClassLoader的緩存中,沒有找到目標class。比方說,第一次加載一個目標類,這個類是不會在緩存的。
(2)ExtClassLoader查找資源時,也不是首先查看自己的地盤是否有這個字節碼文件,而是直接委託給父加載器BootstrapClassLoader。
(3)如果父加載器BootstrapClassLoader在其地盤找到,並且加載成功,則直接返回了;反過來,如果在JVM的核心地盤——%sun.boot.class.path% 中沒有找到。則回到ExtClassLoader查找其地盤。
(4)如果父加載器ExtClassLoader在自己的地盤找到,並且加載成功,也直接返回了;反過來,如果在ExtClassLoader的地盤——%java.ext.dirs% 中沒有找到。則回到AppClassLoader自己的地盤。
(5)於是乎,逗了一大圈,終於回到了自己的地盤。還附帶了兩條件,就是前面的老大們沒有搞定,否則也沒有AppClassLoader啥事情了。
(6)AppClassLoader在自己的地盤找到,這個地盤就是%java.class.path%路徑下查找。找到就返回。
(7)最終,如果沒有找到,就拋出異常了。
這個過程,就是一個典型的雙親委託機制的一次執行流程。
什麼是雙親委託機制呢?
4.4. 雙親委託機制原理與沙箱機制
雙親委派模型的的原理是:
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啓動類加載器中。只有當父加載器反饋自己無法完全這個加載請求時,子加載器纔會嘗試自己去加載。
爲什麼要使用這種雙親委託模式呢?
因爲這樣可以避免重複加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。
雙親委託機制,也就構成了JVM 的類的沙箱機制。
沙箱機制是由基於雙親委派機制上採取的一種JVM的自我保護機制,假設你要寫一個java.lang.String 的類,由於雙親委派機制的原理,此請求會先交給Bootstrap試圖進行加載,但是Bootstrap在加載類時首先通過包和類名查找rt.jar中有沒有該類,有則優先加載rt.jar包中的類,因此就保證了java的運行機制不會被破壞。
4.5. forName方法和loadClass方法的關係
說到這裏,順便回答一下前面提出的一個問題。
前面提到,explicit顯式方式,又分兩種方式:
一是:java.lang.Class的forName()方法;
二是:java.lang.ClassLoader的loadClass()方法。
二者的區別和聯繫是什麼呢?
首先看聯繫:
Class.forName使用的是調用者的類加載器loadClass()方法來加載類的。
其次看區別。
當調用Class.forName(String)載入class時執行,會完整的完成前面提到的五步工作,就是加載、驗證、準備、解析、初始化。
如果調用ClassLoader.loadClass(String)載入class時,會執行加載、驗證、準備、解析的前面四步,並不會執行第五步——初始化。這是,類的static塊沒有被執行。需要在第一次實例化時執行,比如第一次執行 Class.newInstance() 操作時。
4.6. 使用組合而不用繼承
4.7. 各種不同的類加載途徑
Java類不是一次性加載的,而是動態被加載到內存。這是java的一大特點,也稱爲運行時綁定,或動態綁定。
除了通過Java內置的三大加載器,從JVM中系統屬性中設置的三大地盤加載Java類,還存在以下的獲取Class文件途徑:
(1)從ZIP包中讀取。很常見,最終成爲日後JAR,WAR,EAR格式的基礎。
(2)從網絡中獲取。這種場景典型的就是Applet。
(3)運行時計算生成。典型的情景就是java動態代理技術。
(4)從其他文件中生成。典型場景是JSP應用,即由JSP文件生成對應的Class類。
(5)從不方便加入到%java.class.path%其他的文件目錄獲取。
如何實現以上的途徑呢?
具體的方法是:通自定義的類加載器,去加載其他途徑的類。
源碼:
代碼工程: classLoaderDemo.zip
下載地址:在瘋狂創客圈QQ羣文件共享。
瘋狂創客圈:如果說Java是一個武林,這裏的聚集一羣武癡, 交流編程體驗心得
QQ羣鏈接:瘋狂創客圈QQ羣
無編程不創客,無案例不學習。 一定記得去跑一跑案例哦
類加載器 全目錄
5. 入門案例:自定義一個文件系統的自定義classLoader