回顧一下類加載過程
類加載過程:加載->連接->初始化。連接過程又可分為三步:驗證->準備->解析。
一個非數組類的加載階段(加載階段獲取類的二進制字節流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類加載器去控制字節流的獲取方式(重寫一個類加載器的 loadClass()
方法)。數組類型不通過類加載器創建,它由 Java 虛擬機直接創建。
所有的類都由類加載器加載,加載的作用就是將 .class文件加載到內存。
類加載器總結
JVM 中內置了三個重要的 ClassLoader,除了 BootstrapClassLoader 其他類加載器均由 Java 實現且全部繼承自java.lang.ClassLoader
:
- BootstrapClassLoader(啟動類加載器) :最頂層的加載類,由C++實現,負責加載
%JAVA_HOME%/lib
目錄下的jar包和類或者或被-Xbootclasspath
參數指定的路徑中的所有類。 - ExtensionClassLoader(擴展類加載器) :主要負責加載目錄
%JRE_HOME%/lib/ext
目錄下的jar包和類,或被java.ext.dirs
系統變量所指定的路徑下的jar包。 - AppClassLoader(應用程序類加載器) :面向我們用戶的加載器,負責加載當前應用classpath下的所有jar包和類。
雙親委派模型
雙親委派模型介紹
每一個類都有一個對應它的類加載器。系統中的 ClassLoder 在協同工作的時候會默認使用 雙親委派模型 。即在類加載的時候,系統會首先判斷當前類是否被加載過。已經被加載的類會直接返回,否則才會嘗試加載。加載的時候,首先會把該請求委派該父類加載器的 loadClass()
處理,因此所有的請求最終都應該傳送到頂層的啟動類加載器 BootstrapClassLoader
中。當父類加載器無法處理時,才由自己來處理。當父類加載器為null時,會使用啟動類加載器 BootstrapClassLoader
作為父類加載器。
每個類加載都有一個父類加載器,我們通過下面的程序來驗證。
public class ClassLoaderDemo {public static void main(String[] args) {System.out.println("ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader());System.out.println("The Parent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent());System.out.println("The GrandParent of ClassLodarDemo's ClassLoader is " + ClassLoaderDemo.class.getClassLoader().getParent().getParent());}
}
Output
ClassLodarDemo's ClassLoader is sun.misc.Launcher$AppClassLoader@18b4aac2
The Parent of ClassLodarDemo's ClassLoader is sun.misc.Launcher$ExtClassLoader@1b6d3586
The GrandParent of ClassLodarDemo's ClassLoader is null
AppClassLoader
的父類加載器為ExtClassLoader
ExtClassLoader
的父類加載器為null,null并不代表ExtClassLoader
沒有父類加載器,而是 BootstrapClassLoader
。
其實這個雙親翻譯的容易讓別人誤解,我們一般理解的雙親都是父母,這里的雙親更多地表達的是“父母這一輩”的人而已,并不是說真的有一個 Mother ClassLoader 和一個 Father ClassLoader 。另外,類加載器之間的“父子”關系也不是通過繼承來體現的,是由“優先級”來決定。官方API文檔對這部分的描述如下:
The Java platform uses a delegation model for loading classes. The basic idea is that every class loader has a “parent” class loader. When loading a class, a class loader first “delegates” the search for the class to its parent class loader before attempting to find the class itself.
雙親委派模型實現源碼分析
雙親委派模型的實現代碼非常簡單,邏輯非常清晰,都集中在 java.lang.ClassLoader
的 loadClass()
中,相關代碼如下所示。
private final ClassLoader parent;
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) {//父加載器不為空,調用父加載器loadClass()方法處理c = parent.loadClass(name, false);} else {//父加載器為空,使用啟動類加載器 BootstrapClassLoader 加載c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {//拋出異常說明父類加載器無法完成加載請求}if (c == null) {long t1 = System.nanoTime();//自己嘗試加載c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}
雙親委派模型的好處
雙親委派模型保證了Java程序的穩定運行,可以避免類的重復加載(JVM 區分不同類的方式不僅僅根據類名,相同的類文件被不同的類加載器加載產生的是兩個不同的類),也保證了 Java 的核心 API 不被篡改。如果沒有使用雙親委派模型,而是每個類加載器加載自己的話就會出現一些問題,比如我們編寫一個稱為 java.lang.Object
類的話,那么程序運行的時候,系統就會出現多個不同的 Object
類。
如果我們不想用雙親委派模型怎么辦?
自定義加載器的話,需要繼承 ClassLoader
。如果我們不想打破雙親委派模型,就重寫 ClassLoader
類中的 findClass()
方法即可,無法被父類加載器加載的類最終會通過這個方法被加載。但是,如果想打破雙親委派模型則需要重寫 loadClass()
方法
自定義類加載器
除了 BootstrapClassLoader
其他類加載器均由 Java 實現且全部繼承自java.lang.ClassLoader
。如果我們要自定義自己的類加載器,很明顯需要繼承 ClassLoader
。