1. 程式人生 > >【正文】Java類載入器( CLassLoader ) 死磕 4: 神祕的雙親委託機制

【正文】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應用類載入器三者之間的關係。

大致整理了如下類似的一幅圖片:

wps612B.tmp

每一個載入器看護一塊自己的地盤。 啟動Bootstrap 看護的是核心中的核心地盤。

前面講到,ExtClassLoader的父載入器為空。而上圖中,ExtClassLoader的父載入器是Bootstrap 啟動類載入器。

實際上,如果一個載入器的parent為空,其父親載入器就是Bootstrap 啟動類載入器。

如果沒有特別的設定,自定義載入的parent,預設為App應用載入器。


4.3. 類的載入次序


loadClass 關鍵原始碼,已經在前面有介紹。

下面用一張圖,對於類的載入次序,做進一步的介紹。

ClassLoader 4.4.3

一般場景下,載入一個類,是從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%其他的檔案目錄獲取。

如何實現以上的途徑呢?

具體的方法是:通自定義的類載入器,去載入其他途徑的類。