雙親委派模型

一、類加載器

1. 作用

實現(xiàn) 通過一個類的全限定名來獲取描述該類的二進制字節(jié)流 動作,即類的加載動作。

在虛擬機中,每個類加載器都有一個獨立的類名稱空間,故只有在 兩個類的類的全限定名相同,且加載該類的加載器相同 的情況下,才判定相等(包括 equals()isAssignableFrom()、isInstance() 方法及 instanceOf 關鍵字的判斷結果)。

2. 分類

啟動類加載器 (Bootstrap Class Loader)

負責加載存放在 <JAVA_HOME>\lib 目錄,或者被 -Xbootclasspath 參數(shù)所指定的路徑中存放的,且文件名能被識別的類庫(如 rt.jar、tools.jar,文件名不符合目錄正確也不會被加載)加載到 JVM 內(nèi)存中。此加載器無法被 Java 程序直接使用,自定義類加載器若需要委派加載請求給此加載器加載,直接使用 null 代替即可。

擴展類加載器(Extension Class Loader)

負責加載 <JAVA_HOME>\lib\ext 目錄中,或者被 java.ext.dirs 系統(tǒng)變量所指定路徑中的所有類庫。此類庫中存放具有通用性的擴展類庫,且允許用戶自行添加,即擴展機制。在類 sun.misc.Launcher$ExtClassLoader 中以 Java 代碼形式實現(xiàn),故用戶可直接在程序中使用此類加載器加載 Class 文件。JDK 9 中,此擴展類加載器被平臺類加載器替代。

平臺類加載器(Platform Class Loader)

由于模塊化系統(tǒng)中,整個 JDK 都基于模塊化構建,故Java 類庫為滿足模塊化需求,未保留 <JAVA_HOME>\lib\ext 目錄,擴展類加載器也被替換為平臺類加載器。

應用程序類加載器(Application Class Loader)

負責加載用戶類路徑(ClassPath)上所有類庫,開發(fā)者可直接使用此類加載器。由于此加載器在 ClassLoader 類中是方法getSystemClassLoader() 的返回值,故又稱系統(tǒng)類加載器。若用戶未自定義加載器,一般情況下為默認加載器。

自定義類加載器

可通過重寫 ClassLoader 類的 findClass() 方法實現(xiàn)自定義類加載器,以完成某些功能。

二、雙親委派模型

1. 描述

如果一個類加載器收到了類加載的請求,它不會加載自己嘗試加載此類,而是委派請求給父類加載器進行加載。

2. 意義

共享

使 Java 類隨著它的類加載器一起具備了一種 帶有優(yōu)先級的層次關系,通過這種層級關可以避免類的重復加載,當父類加載器已經(jīng)加載了該類時,子 ClassLoader 就沒有必要再加載一次。

隔離

隔離功能,保證核心類庫的純凈和安全,防止惡意加載,避免了 Java 的核心 API 被篡改。

保證唯一

若不采用雙親委派機制,同一個類有可能被多個類加載器加載,這樣該類會被識別為兩個不同的類。

雙親委派機制在很大程度上防止內(nèi)存中出現(xiàn)多個相同的字節(jié)碼文件,加載類的時候默認會使用當前類的 ClassLoader 進行加載,只有當你使用該 class 的時候才會去裝載,一個加載器只會裝載同一個 class 一次。

3. 源碼

源碼比較簡單,全部集中在 java.lang.ClassLoaderloadClass() 方法中。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,檢查請求類是否被加載過了
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 如果父類加載器拋出 ClassNotFoundException,說明父類加載器無法完成加載請求
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 在父類加載器無法加載時,再調(diào)用本身的 findClass 方法進行類加載
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 記錄統(tǒng)級信息
                    ...
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

先檢查請求加載的類型是否已加載過,若沒有則調(diào)用父加載器的 loadClass() 方法;若父類加載器為空則默認使用啟動類作為父加載器。若父類加載器加載失敗拋出 ClassNotFoundException 異常,才調(diào)用自己的 findClass() 方法嘗試進行加載。

4. 雙親委派模型圖

圖1. JDK1.8 雙親委派模型

如圖,即為 JDK 1.8 及以前的雙親委派模型圖,除頂層類加載器外,其余類加載器都必須有自己的父類加載器。類加載器的父子關系一般 不以繼承關系 實現(xiàn),而是 組合關系 復用父加載器代碼。

圖2. JDK 9 雙親委派模型

JDK 9 中因模塊化的加入而重構了目錄結構,也順帶將擴展類加載器替換為平臺類加載器。雖然總體上仍保持三層類加載器和雙親委派架構,但委派關系發(fā)生變動。

當平臺及應用程序類加載器收到類加載請求,在委派給父類加載器前,要先 判斷是否歸屬于某一個系統(tǒng)模塊,如果找到歸屬關系,則優(yōu)先委派給對應模塊的類加載器。由此,也可以算是類加載器的 第四次被破壞。

三、雙親委派模型的三次破壞

雙親委派模型僅僅是 Java 設計者推薦開發(fā)者們的類加載器實現(xiàn)方式,并不是強制約束的模型。截至目前,大多數(shù) Java 圈的類加載器都遵循此模型,但仍出現(xiàn)過三次較大規(guī)模破壞。

1. 兼容 JDK 1.2 前的程序

產(chǎn)生原因

由于雙親委派模型是 JDK 1.2 才被引入,但 Java 第一個版本即存在抽象類 java.lang.ClassLoader,開發(fā)者已編寫好自定義類加載器,若進行 JDK 升級,則會導致 loadClass() 被覆蓋,而正確做法應為重寫 findClass()。

解決方案

為了兼容已存在的用戶自定義類,Java 設計者們只能在 JDK 1.2 后添加一個新的 protected 方法 findClass(),并引導用戶盡可能在類加載邏輯中重寫此方法,而不是在 loadClass() 中編寫代碼。按照 loadClass() 方法邏輯,父類加載失敗,會調(diào)用自己的 findClass() 方法完成加載,保證新代碼也可以符號雙親委派規(guī)則。

2. 自身的缺陷

產(chǎn)生原因

雙親委派模型很好地解決了各個類加載器協(xié)作時基礎類型一致性問題,即越基礎的類由越上層的類加載器加載。但基礎類型也會存在調(diào)用回用戶代碼的場景。

場景

典型的例子便是 JNDI 服務,此服務已是 Java 標準服務。它的代碼由啟動類加載器完成加載(即在 rt.jar 中),屬于 Java 中很基礎的類型。但 JNDI 存在的目的就是對資源進行查找和集中管理,故需要調(diào)用其他廠商實現(xiàn)并部署在應用程序 ClassPath 下的 JNDI 服務提供者接口(SPI),但啟動類不可能識別且加載這些代碼。

解決方案

為解決此問題,Java 設計團隊引入了線程上下文類加載器。此加載器可通過 java.lang.Thread 類的 setContextClassLoader() 方法進行設置,如果創(chuàng)建線程時未設置,它將從父類繼承一個,如果應用程序全局范圍內(nèi)都未設置,則這個類加載器默認為應用程序類加載器。故以此即可加載所需的 SPI 服務代碼。此方式為一種父類加載器請求子類加載器完成類加載的行為。

3. 實現(xiàn)程序動態(tài)性

產(chǎn)生原因

程序動態(tài)性即代碼熱替換、模塊熱部署等功能。

場景

OSGi 是實現(xiàn)熱部署的常用規(guī)范,其實現(xiàn)熱部署的關鍵是它自定義的類加載器機制的實現(xiàn),每一個程序模塊(在 OSGi 中稱為 Bundle)都有一個類加載器,當需要更換一個 Bundle 時,就把 Bundle 連同類加載器一起換掉以實現(xiàn)代碼熱替換。

實現(xiàn)方式

OSGi 環(huán)境下,類加載器不再是雙親委派模型推薦的樹狀結構,而是更加復雜的網(wǎng)狀結構,它的委派關系僅少部分遵守雙親委派模型,其余部分會在平級類加載器中查找。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容