點擊關注公衆號及時獲取筆主最新更新文章,並可免費領取本文檔配套的《Java面試突擊》以及Java工程師必備學習資源。
公衆號JavaGuide 後臺回覆關鍵字“1”,免費獲取JavaGuide配套的Java工程師必備學習資源(文末有公衆號二維碼)。
回顧一下類加載過程
類加載過程:加載->連接->初始化。連接過程又可分爲三步:驗證->準備->解析。
一個非數組類的加載階段(加載階段獲取類的二進制字節流的動作)是可控性最強的階段,這一步我們可以去完成還可以自定義類加載器去控制字節流的獲取方式(重寫一個類加載器的 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 stats
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程序的穩定運行,可以避免類的重複加載(JVM 區分不同類的方式不僅僅根據類名,相同的類文件被不同的類加載器加載產生的是兩個不同的類),也保證了 Java 的核心 API 不被篡改。如果沒有使用雙親委派模型,而是每個類加載器加載自己的話就會出現一些問題,比如我們編寫一個稱爲 java.lang.Object
類的話,那麼程序運行的時候,系統就會出現多個不同的 Object
類。
如果我們不想用雙親委派模型怎麼辦?
爲了避免雙親委託機制,我們可以自己定義一個類加載器,然後重載 loadClass()
即可。
自定義類加載器
除了 BootstrapClassLoader
其他類加載器均由 Java 實現且全部繼承自java.lang.ClassLoader
。如果我們要自定義自己的類加載器,很明顯需要繼承 ClassLoader
。
推薦閱讀
- https://blog.csdn.net/xyang81/article/details/7292380
- https://juejin.im/post/5c04892351882516e70dcc9b
- http://gityuan.com/2016/01/24/java-classloader/
公衆號
如果大家想要實時關注我更新的文章以及分享的乾貨的話,可以關注我的公衆號。
《Java面試突擊》: 由本文檔衍生的專爲面試而生的《Java面試突擊》V2.0 PDF 版本公衆號後臺回覆 “Java面試突擊” 即可免費領取!
Java工程師必備學習資源: 一些Java工程師常用學習資源公衆號後臺回覆關鍵字 “1” 即可免費無套路獲取。