新聞中心
Java類加載器算是一個老生常談的問題,大多Java工程師也都對其中的知識點倒背如流,最近在看源碼的時候發(fā)現(xiàn)有一些細(xì)節(jié)的地方理解還是比較模糊,正好寫一篇文章梳理一下。

關(guān)于Java類加載器的知識,網(wǎng)上一搜一大片,我自己也看過很多文檔,博客。資料雖然很多,但還是希望通過本文盡量寫出一些自己的理解,自己的東西。如果只是重復(fù)別人寫的內(nèi)容那就失去寫作的意義了。
類加載器結(jié)構(gòu)
類加載器結(jié)構(gòu)
名稱解釋:
- 根類加載器,也叫引導(dǎo)類加載器、啟動類加載器。由于它不屬于Java類庫,這里就不說它對應(yīng)的類名了,很多人喜歡稱BootstrapClassLoader。本文都稱之為根類加載器。
加載路徑:\lib - 擴展類加載器,對應(yīng)Java類名為ExtClassLoader,該類是sun.misc.Launcher的一個內(nèi)部類。
加載路徑:\lib\ext - 應(yīng)用類加載器,對應(yīng)Java類名為AppClassLoader,該類是sun.misc.Launcher的一個內(nèi)部類。
加載路徑:用戶目錄
//可以通過這種方式打印加載路徑
System.out.println("boot:"+System.getProperty("sun.boot.class.path"));
System.out.println("ext:"+System.getProperty("java.ext.dirs"));
System.out.println("app:"+System.getProperty("java.class.path"));重點說明:
- 根類加載器對于普通Java工程師來講可以理解成一個概念上的東西,因為我們無法通過Java代碼獲取到根類加載器,它屬于JVM層面。
- 除了根類加載器之外,其他兩個擴展類加載器和應(yīng)用類加載器都是通過類sun.misc.Launcher進行初始化,而Launcher類則由根類加載器進行加載。
看下Launcher初始化源碼:
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//初始化擴展類加載器,注意這里構(gòu)造函數(shù)沒有入?yún)?,即無法獲取根類加載器
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//初始化應(yīng)用類加載器,注意這里的入?yún)⒕褪菙U展類加載器
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//設(shè)置上下文類加載器,這個后面會詳細(xì)說
Thread.currentThread().setContextClassLoader(this.loader);
//刪除了一些安全方面的代碼
//...
}
雙親委派模型
雙親委派模型是指當(dāng)我們調(diào)用類加載器的loadClass方法進行類加載時,該類加載器會首先請求它的父類加載器進行加載,依次遞歸。如果所有父類加載器都加載失敗,則當(dāng)前類加載器自己進行加載操作。
邏輯很簡單,通過ClassLoader類的源碼來分析一下。
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//進行類加載操作時首先要加鎖,避免并發(fā)加載
synchronized (getClassLoadingLock(name)) {
//首先判斷指定類是否已經(jīng)被加載過
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果當(dāng)前類沒有被加載且父類加載器不為null,則請求父類加載器進行加載操作
c = parent.loadClass(name, false);
} else {
//如果當(dāng)前類沒有被加載且父類加載器為null,則請求根類加載器進行加載操作
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
//如果父類加載器加載失敗,則由當(dāng)前類加載器進行加載,
c = findClass(name);
//進行一些統(tǒng)計操作
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
//初始化該類
if (resolve) {
resolveClass(c);
}
return c;
}
}雙親委派模型的實現(xiàn)邏輯總體看還是非常簡單明了的。
這里有幾個細(xì)節(jié)需要說明:
- ClassLoader類是一個抽象類,但卻沒有包含任何抽象方法。
- 如果要實現(xiàn)自己的類加載器且不破壞雙親委派模型,只需要繼承ClassLoader類并重寫findClass方法。
- 如果要實現(xiàn)自己的類加載器且破壞雙親委派模型,則需要繼承ClassLoader類并重寫loadClass,findClass方法。
令人疑惑的系統(tǒng)類加載器
當(dāng)你把上面的知識都搞清楚以后,會發(fā)現(xiàn)ClassLoader類中有個方法是getSystemClassLoader,系統(tǒng)類加載器,這又是什么?
系統(tǒng)類加載器是個容易讓人混淆的概念,我一度以為它就是應(yīng)用類加載器的別名,就跟啟動類加載器和根類加載器道理一樣。事實上, 默認(rèn)情況下我們通過ClassLoader.getSystemClassLoader()獲取到的系統(tǒng)類加載器也確實是應(yīng)用類加載器 。
很多資料在說類加載器結(jié)構(gòu)的時候會直接把應(yīng)用類加載器說成是系統(tǒng)類加載器,其實我們通過類名就可以判斷兩個不是一回事。
系統(tǒng)類加載器可以通過System.setProperty("java.system.class.loader", xxx類名)進行自定義設(shè)置。
系統(tǒng)類加載器不是一個全新的加載器,它只是一個概念,本質(zhì)上還是上述說的四大類加載器(把用戶自定義類加載器算進去),至于提出這個概念的原因以及使用場景,還需要繼續(xù)考究。
被人忽略的上下文類加載器
上面討論了各個類加載器的加載路徑。鑒于雙親委派模型的設(shè)計,子類加載器都保留了父類加載器的引用,也就是說當(dāng)由子類加載器加載的類需要訪問由父類加載器加載的類時,毫無疑問是可以訪問到的。但考慮一種場景,會不會有 父類加載器加載的類需要訪問子類加載器加載的類 這種情況?如果有,怎么解決(父類加載器并沒有子類加載器的引用)?
這就是我們要討論的常常被人們忽略的上下文類加載器。
經(jīng)典案例:
JDBC是Java制定的一套訪問數(shù)據(jù)庫的標(biāo)準(zhǔn)接口,它包含在Java基礎(chǔ)類庫中,也就是說它是由根類加載器加載的。與此同時,各個數(shù)據(jù)庫廠商會各自實現(xiàn)這套接口來讓Java工程師可以訪問自己的數(shù)據(jù)庫,而這部分實現(xiàn)類庫是需要Java工程師在工程中作為一個第三方依賴引入使用的,也就是說這部分實現(xiàn)類庫是由應(yīng)用類加載器進行加載的。
先上一段Java獲取Mysql連接的代碼:
//加載驅(qū)動程序
Class.forName("com.mysql.jdbc.Driver");
//連接數(shù)據(jù)庫
Connection conn = DriverManager.getConnection(url, user, password);這里DriverManager類就屬于Java基礎(chǔ)類庫,由根類加載器加載。我們可以通過它獲取到數(shù)據(jù)庫的連接,顯然是它通過com.mysql.jdbc.Driver驅(qū)動成功連接到了數(shù)據(jù)庫,上面也說了數(shù)據(jù)庫驅(qū)動(作為第三方類庫引入)是由應(yīng)用類加載器加載的。這個場景就是典型的由父類加載器加載的類需要訪問由子類加載器加載的類。
Java是怎么實現(xiàn)這種逆向訪問的呢?直接看DriverManager類的源碼:
//建立數(shù)據(jù)庫連接各個不同參數(shù)的方法最終都會走到這里
private static Connection getConnection(
String url, java.util.Properties info, Class> caller) throws SQLException {
//獲取調(diào)用者的類加載器
ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
synchronized(DriverManager.class) {
//如果為null,則使用上下文類加載器
//這里是重點,什么時候類加載器才會為null? 當(dāng)然就是由根類加載器加載的類了
if (callerCL == null) {
callerCL = Thread.currentThread().getContextClassLoader();
}
}
//...省略
for(DriverInfo aDriver : registeredDrivers) {
//使用上下文類加載器去加載驅(qū)動
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
//如果加載成功,則進行連接
Connection con = aDriver.driver.connect(url, info);
//...
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
}
//...
}
}重點說明:
為什么上下文類加載器就可以加載到數(shù)據(jù)庫驅(qū)動呢?回到上面一開始Launcher初始化類加載器的源碼,我們發(fā)現(xiàn)原來所謂的上下文類加載器本質(zhì)上就是應(yīng)用類加載器,有沒有豁然開朗的感覺? 上下文類加載器只是為了解決類的逆向訪問提出來的一個概念,并不是一個全新的類加載器,它本質(zhì)上就是應(yīng)用類加載器 。
基本上我理解的Java類加載器就這么多知識,如果有沒提到的或者是錯誤的地方,歡迎交流。
網(wǎng)站名稱:一篇文章讀懂Java類加載器
當(dāng)前地址:http://www.5511xx.com/article/dpgehhs.html


咨詢
建站咨詢
