八股文扛把子!五分鐘搞懂 「雙親委派機制」!

追逐仰望星空 發佈 2022-12-29T00:46:33.117660+00:00

說到雙親委派機制,首先你得搞清楚啥是ClassLoader(類加載器)。我們知道Java是運行在JVM虛擬機中的,它是怎麼運行的呢?其實,我們在IDE中編寫的Java原始碼在啟動時,會被編譯器編譯成.class的字節碼文件。

說到雙親委派機制,首先你得搞清楚啥是ClassLoader(類加載器)。

我們知道Java是運行在jvm虛擬機中的,它是怎麼運行的呢?其實,我們在IDE中編寫的java原始碼在啟動時,會被編譯器編譯成.class的字節碼文件。然後由ClassLoader負責將這些class文件給加載到JVM內存中,轉為Class對象再去調用或執行。

JVM預定義了三種類加載器,自上而下包括:Bootstrap ClassLoader(啟動類加載器)、Extension ClassLoader (拓展類加載器)、Application ClassLoader(應用程式類加載器)。當然,也可以自定義多個其他的CustomClassLoader(自定義類加載器)。

在《深入理解java虛擬機》一書中,針對我們常用的Tomcat伺服器,描述了Tomcat自定義了多個類加載器,這些類加載器按照經典的雙親委派模型來實現,如下圖所示:

為了方便理解,本文僅基於主要的三種進行解釋,其餘自定義類加載器不再贅述。

  • Bootstrap ClassLoader(啟動類加載器):主要負責加載核心的類庫(如java.lang.*),JVM_HOME/lib目錄下的jar,以及構造Extension ClassLoader 和 Application ClassLoader 這兩類加載器。具體啟動類加載器加載到的路徑可通過System.getProperty(「sun.boot.class.path」)查看。
  • Extension ClassLoader(拓展類加載器):主要負責加載jre/lib/ext目錄下的一些擴展的jar。具體啟動類加載器加載到的路徑可通過System.getProperty("java.ext.dirs")查看。
  • Application ClassLoader(應用程式類加載器):主要負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器,默認就是用這個加載器。具體啟動類加載器加載到的路徑可通過System.getProperty("java.class.path")查看。

CustomClassLoader(其他的自定義類加載器):主要負責加載應用程式的主函數類

如上圖所示;對於預定義的三種類加載器,首先會在Application ClassLoader中檢查是否加載過,如果之前加載過那就無需再加載了,每一級的類加載器都有自己的緩存,直接從緩存中取出使用;

如果Application ClassLoader沒有加載過,那麼會拿到父加載器,調用父加載器的loadClass方法。其父類同理也會先檢查自己是否已經加載過,如果沒有再往上。類似遞歸的檢查過程,截至到達Bootstrap classLoader之前,都是在檢查是否加載過,並不會選擇自己去加載。

直到Bootstrap ClassLoader,已經沒有父加載器了,這時候說明該.class必須重新加載,首先考慮自己是否能加載了,如果自己無法加載,會下沉到子加載器去加載,一直到最底層,如果沒有任何加載器能加載,就會拋出ClassNotFoundException。

  那麼有同學問了,為什麼要從Application ClassLoader開始加載?想要實現雙親委派,直接從Bootstrap ClassLoader 開始加載不就行了?為什麼還要向上委派一次?

原理上講雙親委派機制是向上查找,向下加載。向上查找是因為每個加載器有一個緩存,如果向上查找的時候發現加載器裡面有數據了就直接返回不需要去jar包裡面查找加載了,如果沒有在向上查找,如果都沒有再向下加載,節省資源,感覺也算是時間換空間。

可以在你的IDE中搜索下ClassLoader,然後打開java.lang包下的ClassLoader類。然後找到loadClass方法,如下:

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先,檢查是否已經被類加載器加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    // 存在父加載器,遞歸的交由父加載器
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        // 直到最上面的Bootstrap類加載器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

按照雙親委派模型來加載類感覺好麻煩,JDK為什麼要這麼玩兒呢?

  • 提高安全性

為了保護系統核心類不被篡改。如果用戶編寫了一個 java.lang.Object 這種核心類,功能和系統 Object 類相同,卻可能植入了惡意代碼。有了雙親委派模型,自定義的 Object 類是不會被加載的,JVM啟動時就會通過bootstarp類加載器把rt.jar下面的Object類加載進來,而不會加載自定義的 Object 類。因此自己重寫的同名類永遠不會被加載。

那如果rt.jar下的核心類被改了呢?其實也不用擔心,jvm類加載流程是加載並驗證,有驗證那些字節碼文件是否合法的程序,你修改了就不屬於合法的字節碼文件了。

  • 防止程序混亂

首先明確,jvm判定兩個對象同屬於一個類型:同名類實例化,實例對應的同名類的加載器必須相同。

要是每個加載器都自己加載的話,那麼可能會出現多個 Object 類,導致混亂。

原文連結:https://blog.csdn.net/qq_39390545/article/details/123491748

關鍵字: