無(wú)論你是跟同事、同學(xué)是鬼、上下級(jí)肤舞、同行、或者面試官討論技術(shù)問(wèn)題的時(shí)候均蜜,很容易卷入JVM大型撕逼現(xiàn)場(chǎng)李剖。為了能夠讓大家從大型撕逼現(xiàn)場(chǎng)中脫穎而出,最近我苦思冥想如何把知識(shí)點(diǎn)盡可能呈現(xiàn)的容易理解囤耳,方便記憶杖爽。于是就開(kāi)啟了這一系列文章的編寫。為了讓JVM相關(guān)知識(shí)點(diǎn)能夠形成一個(gè)體系紫皇,arthinking將編寫整理一系列的專題,以盡量以圖片的方式描述相關(guān)知識(shí)點(diǎn)腋寨,并且最終把所有相關(guān)知識(shí)點(diǎn)串成了一張圖聪铺。持續(xù)更新中,歡迎大家閱讀萄窜。有任何錯(cuò)落之處也請(qǐng)您高抬貴手幫忙指正铃剔,感謝撒桨!
導(dǎo)讀:
- 類加載器是怎么被創(chuàng)建出來(lái)的?
- 什么是雙親委派機(jī)制键兜?為什么要有這種機(jī)制凤类?
- Class實(shí)例和類加載器究竟是在Java Heap中,還是在方法區(qū)中普气?
類加載器: 可以實(shí)現(xiàn)通過(guò)一個(gè)類的全限定名稱來(lái)獲取描述此類的二進(jìn)制字節(jié)流谜疤。實(shí)現(xiàn)這個(gè)動(dòng)作的代碼模塊成為”類加載器“。
通過(guò)自定義類加載器可以實(shí)現(xiàn)各種有趣而強(qiáng)大的功能更:OSGi现诀,熱部署夷磕,代碼加密等。
1仔沿、類加載器的加載流程
如上圖為類加載器的加載流程坐桩。
這里簡(jiǎn)單描述下:
1.1、啟動(dòng)類加載器
啟動(dòng)類加載器:系統(tǒng)啟動(dòng)的時(shí)候封锉,首先會(huì)通過(guò)由C++實(shí)現(xiàn)的啟動(dòng)類加載器绵跷,加載<JAVA_HOME>/lib目錄下面的jar包,或者被-Xbootclasspath參數(shù)指定的路徑并且被虛擬機(jī)識(shí)別的文件名的jar包成福。把相關(guān)Class加載到方法區(qū)中碾局。
這一步會(huì)加載關(guān)鍵的一個(gè)類:sun.misc.Launcher
。這個(gè)類包含了兩個(gè)靜態(tài)內(nèi)部類:
- ExtClassLoader:擴(kuò)展類加載器內(nèi)部類闷叉,下面會(huì)講擦俐;
- AppClassLoader:應(yīng)用程序類加載器內(nèi)部類,下面會(huì)講
可以反編譯rt.jar文件查看詳細(xì)代碼:
在加載到Launcher類完成后握侧,會(huì)對(duì)該類進(jìn)行初始化蚯瞧,初始化的過(guò)程中,會(huì)創(chuàng)建 ExtClassLoader 和 AppClassLoader品擎,源碼如下:
public Launcher() {
ExtClassLoader extClassLoader;
try {
extClassLoader = ExtClassLoader.getExtClassLoader();
} catch (IOException iOException) {
throw new InternalError("Could not create extension class loader", iOException);
}
try {
this.loader = AppClassLoader.getAppClassLoader(extClassLoader);
} catch (IOException iOException) {
throw new InternalError("Could not create application class loader", iOException);
}
Thread.currentThread().setContextClassLoader(this.loader);
...
由于啟動(dòng)類加載器是由C++實(shí)現(xiàn)的埋合,所以在Java代碼里面是訪問(wèn)不到啟動(dòng)類加載器的,如果嘗試通過(guò)String.class.getClassLoader()
獲取啟動(dòng)類的引用萄传,會(huì)返回null
甚颂;
問(wèn)題:
啟動(dòng)類加載器,擴(kuò)展類加載器和應(yīng)用類加載器都是又誰(shuí)加載的秀菱?
- 啟動(dòng)類加載器是JVM的內(nèi)部實(shí)現(xiàn)振诬,在JVM申請(qǐng)好內(nèi)存之后,由JVM創(chuàng)建這個(gè)啟動(dòng)類加載器
- 擴(kuò)展類加載器和應(yīng)用程序類加載器是由啟動(dòng)類加載器加載進(jìn)來(lái)的衍菱;
說(shuō)說(shuō)以下代碼輸出什么:
public static void main(String[] args) { System.out.println("加載當(dāng)前類的加載器:" + TestClassLoader.class.getClassLoader()); System.out.println("加載應(yīng)用程序類加載器的加載器" + TestClassLoader.class.getClassLoader().getClass().getClassLoader()); System.out.println("String類的啟動(dòng)類加載器" + String.class.getClassLoader()); }
1.2赶么、擴(kuò)展類加載器
如上圖,擴(kuò)展類加載器負(fù)責(zé)加載<JAVA_HOME>/lib/ext目錄下或者被java.ext.dirs系統(tǒng)變量指定的路徑中的類脊串。
1.3辫呻、應(yīng)用程序類加載器
引用程序類加載器加載用戶類路徑下制定的類庫(kù)清钥,如果應(yīng)用程序沒(méi)有自定義過(guò)自己的類加載器,此類加載器就是默認(rèn)的類加載器放闺。
引用程序類加載器也叫系統(tǒng)類加載器祟昭,可以通過(guò)getSystemClassLoader
方法得到應(yīng)用程序類加載器。
注意怖侦,如上圖通過(guò)以上三個(gè)類加載器加載類到方法區(qū)之后篡悟,方法區(qū)中分別對(duì)應(yīng)有各自的類信息存儲(chǔ)區(qū)。不同類加載器加載的同一個(gè)類文件不相等础钠。
2恰力、類加載器的雙親委派機(jī)制
2.1、雙親委派機(jī)制原理
雙親委派模型在JDK1.2之后被引入旗吁,并廣泛使用踩萎,這不是一個(gè)強(qiáng)制性的約束模型,二貨思Java設(shè)計(jì)者推薦給開(kāi)發(fā)者的一種類加載器實(shí)現(xiàn)方式很钓。我們也可以覆蓋對(duì)應(yīng)的方式香府,實(shí)現(xiàn)自己的加載模型。
類加載器的雙親委派機(jī)制如下:
也就是說(shuō):
- 一個(gè)類加載器收到了類加載請(qǐng)求码倦,不會(huì)自己立刻嘗試加載類企孩,而是把請(qǐng)求委托給父加載器去完成,每一層都是如此袁稽,所有的家在請(qǐng)求最終都傳遞到最頂層的類加載器進(jìn)行處理勿璃;
- 如果父加載器不存在了,那么嘗試判斷有沒(méi)有被啟動(dòng)類加載器加載推汽;
- 如果的確沒(méi)有被夾在补疑,則再自己嘗試加載。
問(wèn)題:
- 為什么要有這么復(fù)雜的雙親委派機(jī)制歹撒?
- 如果沒(méi)有這種機(jī)制莲组,我們就可以篡改啟動(dòng)類加載器中需要的類了,如暖夭,修自己編寫一個(gè)
java.lang.Object
用自己的類加載器進(jìn)行加載锹杈,系統(tǒng)中就會(huì)存在多個(gè)Object類,這樣Java類型體系最基本的行為也就無(wú)法保證了迈着。
2.2竭望、雙親委派機(jī)制處理流程
JDK中默認(rèn)的雙親委派處理流程是怎么的呢?接下來(lái)我們看看代碼裕菠,以下是java.lang.ClassLoader.loadClass()
方法的實(shí)現(xiàn):
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
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.
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;
}
}
轉(zhuǎn)成流程圖咬清,即是:
如山圖所以,總是先回嘗試讓父類加載器先加載,其次判斷啟動(dòng)類加載器是否已經(jīng)加載了枫振,最后才嘗試從當(dāng)前類加載器加載。轉(zhuǎn)換為更清晰的模型如下:
雙親委派模型具有以下特點(diǎn):
- 可見(jiàn)性原則:
- 應(yīng)用類加載器是可以讀取到由擴(kuò)展類加載器和啟動(dòng)類加載器加載進(jìn)來(lái)的Class的萤彩;
- 擴(kuò)展類加載器是可以讀取到由啟動(dòng)類加載器加載進(jìn)來(lái)的Class的粪滤;
- 唯一性:
- 類是唯一的,沒(méi)有重復(fù)的類雀扶;
2.3杖小、類加載器和Class實(shí)例的題外話
啟動(dòng)類加載器,擴(kuò)展類加載器愚墓,應(yīng)用程序類加載器予权,他們分別管理者各自方法區(qū)里的一個(gè)區(qū)塊。
根據(jù)上一篇文章我們知道浪册,方法區(qū)里面主要存儲(chǔ)的是類的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)扫腺,這個(gè)類的在方法區(qū)中的各種數(shù)據(jù)結(jié)構(gòu)信息通過(guò)類的Class實(shí)例進(jìn)行訪問(wèn)。
如下圖:
方法區(qū)里面存儲(chǔ)著加載進(jìn)來(lái)的類信息村象,方法區(qū)同時(shí)雇傭了兩類工種幫忙干活:
- 類加載器:負(fù)責(zé)管理各個(gè)存儲(chǔ)區(qū)的類信息笆环,如加載和卸載類信息;
- Class實(shí)例:負(fù)責(zé)對(duì)接外部需求厚者,如果外部有人想查看里面的類信息躁劣,則需要通過(guò)Class實(shí)例來(lái)獲仍础端壳;
另外,方法區(qū)里面佑吝,啟動(dòng)類加載器類信息對(duì)擴(kuò)展兩類加載器類信息可見(jiàn)熙宇,而前面兩者的類信息又對(duì)應(yīng)用程序類加載器類信息可見(jiàn)鳖擒。
3、其他非雙親委派模型的案例
3.1奇颠、JDK 1.0遺留問(wèn)題
在JDK1.0已經(jīng)存在了ClassLoader類败去,但是當(dāng)時(shí)還沒(méi)有雙親委派機(jī)制,用戶為了自定義類加載器烈拒,需要重新loadClass()
方法圆裕,而我們知道,在JDK1.2以后荆几,loadClass里面就是雙親委派機(jī)制的實(shí)現(xiàn)代碼吓妆,此時(shí),要實(shí)現(xiàn)自定義類加載器吨铸,需要重新findClass()類即可行拢。
如果重新了loadClass()方法,也就意味著不再遵循雙親委派模型了诞吱。
3.2舟奠、線程上下文類加載器
為什么需要這個(gè)東西呢竭缝,我們還是從一個(gè)案例來(lái)說(shuō)起。
Tomcat中的類加載器
我們知道Tomcat目錄結(jié)構(gòu)中有以下目錄:
/common/: 該目錄下的類庫(kù)可被Tomcat和所有的WebApp共同使用沼瘫;
/server/: 該目錄下的類庫(kù)可被Tomcat使用抬纸,但對(duì)所有的WebApp不可見(jiàn);
/shared/: 該目錄下的類庫(kù)可被所有的WebApp共同使用耿戚,但對(duì)Tomcat自己不可見(jiàn)湿故;
另外Web應(yīng)用程序還有自身的類庫(kù),放在/WebApp/WEB-INF
目錄中:這里面的類庫(kù)僅僅可以被此Web應(yīng)用程序使用膜蛔,對(duì)Tomcat和其他Web應(yīng)用程序都不可見(jiàn)坛猪。
為了實(shí)現(xiàn)以上各個(gè)目錄的類庫(kù)可見(jiàn)性效果,Tomat提供了如下的自定義類加載器:
現(xiàn)在如下場(chǎng)景:
我們發(fā)現(xiàn)Tomcat下面有若干個(gè)webapp皂股,每個(gè)webapp都用到了spring墅茉,于是我們把spring的jar包放到了shared
目錄中。
于是問(wèn)題出現(xiàn)了:由于spring的jar包是由Shared類加載器加載的屑墨,假設(shè)我們要使用SpringContext的getBean方法躁锁,獲取webapp中的Bean,如果是按照雙親委派模型卵史,就會(huì)有問(wèn)題了战转,因?yàn)閣ebapp中的Java類是對(duì)SharedClassLoader不可見(jiàn)的:
Spring中的線程上下文類加載器
為了解決這個(gè)問(wèn)題,Spring使用了線程上下文類加載器以躯,即從ThreadLocal中獲取到當(dāng)前線程的上下文類加載器槐秧,來(lái)加載所有的類庫(kù)和類。
關(guān)于Spring初始化源碼相關(guān)解讀忧设,參考我的這邊文章:Spring IoC原理剖析刁标。
Spring中的bean類加載器
ApplicationContext中有一個(gè)beanClassLoader
字段,這個(gè)是bean的類加載器址晕,在prepareBeanFactory()方法中做了初始化:
beanFactory.setBeanClassLoader(getClassLoader());
getClassLoader方法如下:
@Override
@Nullable
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
ClassUtils.getDefaultClassLoader()方法:
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
可以發(fā)現(xiàn)膀懈,這里最終取了當(dāng)前線程上下文中的ClassLoader。
加載Bean
我們來(lái)看看Spring加載Class的代碼谨垃。這里我們直接找到實(shí)例化Singletons的方法跟進(jìn)去找需要關(guān)注的代碼:
我們發(fā)現(xiàn)在加載Bean Class的時(shí)候調(diào)用了這個(gè)方法:
AbstractBeanFactory:
ClassLoader beanClassLoader = getBeanClassLoader();
也就是用到了ApplicationContext中的beanClassLoader
启搂,線程上下文類加載器來(lái)加載Bean Class實(shí)例。
總結(jié)
Spring作為一個(gè)第三方類庫(kù)刘陶,可能被任何的ClassLoader加載胳赌,所以最靈活的方式是直接使用上下文類加載器。
3.3匙隔、模塊熱部署
主要是類似OSGi這類的模塊化熱部署技術(shù)疑苫。在OSGi中不再是雙親委派模型中的樹狀結(jié)構(gòu),而是更復(fù)雜的網(wǎng)狀結(jié)構(gòu)。
References
Where are static methods and static variables stored in Java?
《深入理解Java虛擬機(jī)-JVM高級(jí)特性與最佳實(shí)踐》
Chapter 5. Loading, Linking, and Initializing
本文為arthinking
基于相關(guān)技術(shù)資料和官方文檔撰寫而成捍掺,確保內(nèi)容的準(zhǔn)確性撼短,如果你發(fā)現(xiàn)了有何錯(cuò)漏之處,煩請(qǐng)高抬貴手幫忙指正挺勿,萬(wàn)分感激阔加。
大家可以關(guān)注我的博客:itzhai.com
獲取更多文章,我將持續(xù)更新后端相關(guān)技術(shù)满钟,涉及JVM、Java基礎(chǔ)胳喷、架構(gòu)設(shè)計(jì)湃番、網(wǎng)絡(luò)編程、數(shù)據(jù)結(jié)構(gòu)吭露、數(shù)據(jù)庫(kù)吠撮、算法、并發(fā)編程讲竿、分布式系統(tǒng)等相關(guān)內(nèi)容泥兰。
如果您覺(jué)得讀完本文有所收獲的話,可以關(guān)注
我的賬號(hào)题禀,或者點(diǎn)贊
的鞋诗,您的支持就是我寫作的動(dòng)力!關(guān)注我的公眾號(hào)迈嘹,及時(shí)獲取最新的文章削彬。
本文作者: arthinking
博客鏈接: https://www.itzhai.com/jvm/what-is-classloader-and-what-is-parents-delegation-model.html
版權(quán)聲明: 版權(quán)歸作者所有,未經(jīng)許可不得轉(zhuǎn)載秀仲,侵權(quán)必究融痛!聯(lián)系作者請(qǐng)加公眾號(hào)。