月薪過萬必會的:雙親委託模型

類加載器簡介

在介紹雙親委託模型以前,先介紹一下類加載器。類加載器經過一個類的全限定名來轉換爲描述這個類的二進制字節流。java

對於任意一個類,被同一個類加載器加載後都是惟一的,但若是被不一樣加載器加載後,就不是惟一的了。即便是源於同一個Class文件、被同一個JVM加載,只要加載類的加載器不一樣,那麼類就不一樣。微信

如何判斷類是否相同,可使用Class對象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回結果進行判斷,也可使用instanceof關鍵字進行對象所屬關係的判斷。
下面咱們寫一個不一樣類加載器加載後的類,看一下對instanceof關鍵字運算有什麼影響:框架

public class OneMoreStudy {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream inputStream = getClass().getResourceAsStream(fileName);
                    if (inputStream == null) {
                        return super.loadClass(name);
                    }
                    byte[] array = new byte[inputStream.available()];
                    inputStream.read(array);
                    return defineClass(name, array, 0, array.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object object = myLoader.loadClass("OneMoreStudy").newInstance();

        System.out.println("class name: " + object.getClass().getName());
        System.out.println("instanceof: " + (object instanceof OneMoreStudy));
    }
}

運行結果:ide

class name: OneMoreStudy
instanceof: false

在運行結果中,第一行能夠看出這個對象確實是OneMoreStudy類實例化出來的,但在第二行中instanceof運算結果是false,說明在JVM中存在兩個OneMoreStudy類,一個是由系統應用程序類加載器加載的,另外一個是由咱們自定義的類加載器加載的。雖然都是來自同一個Class文件,在同一個JVM裏,可是被不一樣的類加載器加載後,仍然是兩個獨立的類。模塊化

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。ui

類加載器的劃分

除了像上面例子代碼中,咱們本身實現的自定義類加載器,還有3種系統提供的類加載器:spa

  1. 啓動類加載器(Bootstrap ClassLoader):它負責將存放在%JAVA_HOME%\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中的,而且是JVM識別的類庫加載到JVM內存中。它僅按照文件名識別,如rt.jar,名字不符合的類庫即便放在lib目錄中也不會被加載。它是由C++語言實現的,沒法被Java程序直接引用。線程

  2. 擴展類加載器(Extension ClassLoader):它負責加載%JAVA_HOME%\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫。它由sun.misc.Launcher.ExtClassLoader實現,開發者能夠直接使用擴展類加載器。設計

  3. 應用程序類加載器(Application ClassLoader):它負責加載用戶類路徑(ClassPath)上所指定的類庫。因爲它是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也稱它爲系統類加載器。它由sun.misc.Launcher.AppClassLoader來實現,開發者能夠直接使用這個類加載器,若是應用程序中沒有自定義過本身的類加載器,通常狀況下這個就是程序中默認的類加載器。rest

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

雙親委託模型

以前提到,對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立其在JVM中的惟一性。但是有這麼多種的類加載器,如何保證一個類在JVM中的惟一性呢?爲了解決這個問題,雙親委託模型(Parents Delegation Model)應運而生,它就是下圖所展現的類加載器之間的層次關係:

除了頂層的啓動類加載器外,其他的類加載器都必須有本身的父類加載器。類加載器之間的父子關係,通常不會以繼承的關係來實現,而是都使用組合關係來複用父類加載器。

類加載器收到類加載的請求後,它不會首先本身去嘗試加載這個類,而是把這個請求委派給父類加載器去嘗試加載。每個類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中。只有當父類加載器反饋本身沒法完成這個加載請求(它的搜索範圍中沒有找到所需的類)時,子加載器纔會嘗試本身去加載。這樣就保證了類在JVM中的惟一性,也保證了Java程序穩定運做。

實現雙親委派模型的代碼都集中在java.lang.ClassLoader的loadClass()方法之中,以下:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //首先,檢查該類是否已經被加載過了
        Class<?> c = findLoadedClass(name);
        //若是沒有加載過,就調用父類加載器的loadClass()方法
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    //若是父類加載器爲空,就使用啓動類加載器
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //若是在父類加載器中找不到該類,就會拋出ClassNotFoundException
            }

            if (c == null) {
                //若是父類找不到,就調用findClass()來找到該類。
                long t1 = System.nanoTime();
                c = findClass(name);
                
                //記錄統計數據
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

破壞雙親委派模型

雙親委派模型並非一個強制性的約束模型,而是Java設計者們推薦給開發者們的類加載器實現方式。大部分的類加載器都遵循這個模型,但也有例外的狀況,好比下面這三種狀況:

重寫ClassLoader的loadClass()方法

在上面例子代碼中,就是重寫了ClassLoader的loadClass()方法,破壞了雙親委派模型,產生了不惟一的類。因此,不提倡開發人員覆蓋loadClass()方法,而應當把本身的類加載邏輯寫到findClass()方法中,在loadClass()方法的邏輯裏若是父類加載失敗,則會調用本身的findClass()方法來完成加載,這樣就能夠保證新寫出來的類加載器是符合雙親委派模型。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

SPI(服務提供者接口)

Java提供了不少SPI(Service Provider Interface,服務提供者接口),容許第三方爲這些接口提供實現,常見的SPI有JDBC、JNDI、JCE、JAXB和JBI等。

SPI的接口由Java核心庫來提供,而這些SPI的實現代碼則是做爲Java應用所依賴的jar包被包含進類路徑(ClassPath)裏。SPI接口中的代碼常常須要加載具體的實現類。那麼問題來了,SPI的接口是Java核心庫的一部分,是由啓動類加載器來加載的;SPI的實現類是由系統類加載器來加載的。引導類加載器是沒法找到SPI的實現類的,由於依照雙親委派模型,啓動類加載器沒法委派系統類加載器來加載類。

這時候就會使用線程上下文類加載器(Thread Context ClassLoader),在JVM中會把當前線程的類加載器加載不到的類交給線程上下文類加載器來加載,直接使用Thread.currentThread().getContextClassLoader()來得到,默認返回的就是應用程序類加載器,也能夠經過java.lang.Thread類的setContextClassLoader()方法進行設置。

而線程上下文類加載器破壞了雙親委派模型,也就是父類加載器請求子類加載器去完成類加載的動做,但爲了實現功能,這也是一種巧妙的實現方式。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

OSGi(開放服務網關協議)

OSGi(Open Service Gateway Initiative,開放服務網關協議)技術是面向Java動態化模塊化系統模型,程序模塊(稱爲Bundle)無需從新引導能夠被遠程安裝、啓動、升級和卸載。實現程序模塊熱部署的關鍵則是它自定義的類加載器機制的實現。

在OSGi中,類加載器再也不是雙親委派模型中的樹狀結構,而是一個較爲複雜的網狀結構,類加載的規則簡要介紹以下:

  1. 若類屬於java.*包,則將加載請求委託給父加載器
  2. 若類定義在啓動委託列表(org.osgi.framework.bootdelegation)中,則將加載請求委託給父加載器
  3. 若類屬於在Import-Package中定義的包,則框架經過ClassLoader依賴關係圖找到導出此包的Bundle的ClassLoader,並將加載請求委託給此ClassLoader
  4. 若類資源屬於在Require-Bundle中定義的Bundle,則框架經過ClassLoader依賴關係圖找到此Bundle的ClassLoader,將加載請求委託給此ClassLoader
  5. Bundle搜索本身的類資源( 包括Bundle-Classpath裏面定義的類路徑和屬於Bundle的Fragment的類資源)
  6. 若類在DynamicImport-Package中定義,則開始嘗試在運行環境中尋找符合條件的Bundle

若是在通過上面一系列步驟後,仍然沒有正確地加載到類資源,則會向外拋出類未發現異常。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

總結

類加載器經過一個類的全限定名來轉換爲描述這個類的二進制字節流,可劃分爲啓動類加載器擴展類加載器應用程序類加載器自定義類加載器。在雙親委託模型中,將上述各類類加載器組成一系列的父子關係,子類加載器先把類加載請求委派給父類加載器去嘗試加載,父類加載器沒法加載時子類加載器才本身嘗試加載,這樣保證了類在JVM中的惟一性。不過,也不遵循雙親委託模型的狀況,好比:重寫ClassLoader的loadClass()方法、SPI(服務提供者接口)、OSGi(開放服務網關協議)。

歡迎關注微信公衆號:萬貓學社,每週一分享Java技術乾貨。

相關文章
相關標籤/搜索