Java中的ClassLoader雙親委派機制詳解
Java ClassLoader雙親委派機制
什么是雙親委派模型
“類加載體系”及ClassLoader雙親委派機制。java程序中的 .java
文件編譯完會生成 .class
文件,而 .class
文件就是通過被稱為類加載器的ClassLoader加載的,而ClassLoder在加載過程中會使用“雙親委派機制”來加載 .class
文件。
- BootStrapClassLoader:啟動類加載器,該ClassLoader是jvm在啟動時創(chuàng)建的,用于加載 $JAVA_HOME/jre/lib下面的類庫(或者通過參數(shù)-Xbootclasspath指定)。由于引導(dǎo)類加載器涉及到虛擬機本地實現(xiàn)細節(jié),開發(fā)者無法直接獲取到啟動類加載器的引用,所以不能直接通過引用進行操作(由c++編寫)。
- ExtClassLoader:擴展類加載器,該ClassLoader是在sun.misc.Launcher里作為一個內(nèi)部類ExtClassLoader定義的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader會加載 $JAVA_HOME/jre/lib/ext下的類庫(或者通過參數(shù)-Djava.ext.dirs指定)。
- AppClassLoader:應(yīng)用程序類加載器,該ClassLoader同樣是在sun.misc.Launcher里作為一個內(nèi)部類AppClassLoader定義的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader會加載java環(huán)境變量CLASSPATH所指定的路徑下的類庫,而CLASSPATH所指定的路徑可以通過System.getProperty(“java.class.path”)獲??;當(dāng)然,該變量也可以覆蓋,可以使用參數(shù)-cp,例如:java -cp 路徑 (可以指定要執(zhí)行的class目錄)。
- CustomClassLoader:自定義類加載器,該ClassLoader是指我們自定義的ClassLoader,比如tomcat的StandardClassLoader屬于這一類;當(dāng)然,大部分情況下使用AppClassLoader就足夠了。
ClassLoader的loadClass(String name, boolean resolve)源碼:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { try { if (parent != null) { c = parent.loadClass(name, false); } else { 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); } } if (resolve) { resolveClass(c); } return c; }
代碼很明朗:
首先找緩存(findLoadedClass),沒有的話就判斷有沒有parent,有的話就用parent來遞歸的loadClass,然而ExtClassLoader并沒有設(shè)置parent,則會通過findBootstrapClassOrNull來加載class,而findBootstrapClassOrNull則會通過JNI方法
private native Class findBootstrapClass(String name)
來使用BootStrapClassLoader來加載class。
然后如果parent未找到class,則會調(diào)用findClass來加載class,findClass是一個protected的空方法,可以覆蓋它以便自定義class加載過程。
另外,雖然ClassLoader加載類是使用loadClass方法,但是鼓勵用 ClassLoader 的子類重寫 findClass(String),而不是重寫loadClass,這樣就不會覆蓋了類加載默認的雙親委派機制。
雙親委派機制為什么安全
舉個例子,ClassLoader加載的class文件來源很多,比如編譯器編譯生成的class、或者網(wǎng)絡(luò)下載的字節(jié)碼。而一些來源的class文件是不可靠的,比如我可以自定義一個java.lang.Integer類來覆蓋jdk中默認的Integer類,
例如下面這樣:
package java.lang; public class Integer { public Integer(int value) { System.exit(0); } }
初始化這個Integer的構(gòu)造器是會退出JVM,破壞應(yīng)用程序的正常進行,如果使用雙親委派機制的話該Integer類永遠不會被調(diào)用,因為委托BootStrapClassLoader加載后會加載JDK中的Integer類而不會加載自定義的這個,可以看下下面這測試個用例:
public static void main(String... args) { Integer i = new Integer(1); System.err.println(i); }
執(zhí)行時JVM并未在new Integer(1)時退出,說明未使用自定義的Integer,于是就保證了安全性。
為什么需要雙親委派模型
為什么需要雙親委派模型呢?假設(shè)沒有雙親委派模型,試想一個場景:
黑客自定義一個java.lang.String類,該String類具有系統(tǒng)的String類一樣的功能,只是在某個函數(shù)稍作修改。比如equals函數(shù),這個函數(shù)經(jīng)常使用,如果在這這個函數(shù)中,黑客加入一些“病毒代碼”。并且通過自定義類加載器加入到JVM中。此時,如果沒有雙親委派模型,那么JVM就可能誤以為黑客自定義的java.lang.String類是系統(tǒng)的String類,導(dǎo)致“病毒代碼”被執(zhí)行。
而有了雙親委派模型,黑客自定義的java.lang.String類永遠都不會被加載進內(nèi)存。因為首先是最頂端的類加載器加載系統(tǒng)的java.lang.String類,最終自定義的類加載器無法加載java.lang.String類。
或許你會想,我在自定義的類加載器里面強制加載自定義的java.lang.String類,不去通過調(diào)用父加載器不就好了嗎?確實,這樣是可行。但是,在JVM中,判斷一個對象是否是某個類型時,如果該對象的實際類型與待比較的類型的類加載器不同,那么會返回false。
舉個簡單例子:
- ClassLoader1、ClassLoader2都加載java.lang.String類,對應(yīng)Class1、Class2對象。
- 那么Class1對象不屬于ClassLoad2對象加載的java.lang.String類型。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
IntelliJ IDEA快速創(chuàng)建getter和setter方法
這篇文章主要介紹了IntelliJ IDEA快速創(chuàng)建getter和setter方法,本文通過圖文實例相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03