【正文】Java類載入器( CLassLoader ) 死磕 4: 神祕的雙親委託機制
【正文】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 |> 載入器:[email protected] showLoaderTree |> [email protected] showLoaderTree |> [email protected]
這個說明,當前載入器型別為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%其他的檔案目錄獲取。
如何實現以上的途徑呢?
具體的方法是:通自定義的類載入器,去載入其他途徑的類。