說到雙親委派機制,首先你得搞清楚啥是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