一篇圖文徹底弄懂類加載器與雙親委派機(jī)制

無(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)讀:

  1. 類加載器是怎么被創(chuàng)建出來(lái)的?
  2. 什么是雙親委派機(jī)制键兜?為什么要有這種機(jī)制凤类?
  3. 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仔沿、類加載器的加載流程

image-20200105144705999

如上圖為類加載器的加載流程坐桩。

這里簡(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ì)代碼:

image-20200105124613663
image-20200105131342939

在加載到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)題:

  1. 啟動(dòng)類加載器,擴(kuò)展類加載器和應(yīng)用類加載器都是又誰(shuí)加載的秀菱?

    1. 啟動(dòng)類加載器是JVM的內(nèi)部實(shí)現(xiàn)振诬,在JVM申請(qǐng)好內(nèi)存之后,由JVM創(chuàng)建這個(gè)啟動(dòng)類加載器
    2. 擴(kuò)展類加載器和應(yīng)用程序類加載器是由啟動(dòng)類加載器加載進(jìn)來(lái)的衍菱;
  2. 說(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ī)制如下:

image-20200105170731274

也就是說(shuō):

  • 一個(gè)類加載器收到了類加載請(qǐng)求码倦,不會(huì)自己立刻嘗試加載類企孩,而是把請(qǐng)求委托給父加載器去完成,每一層都是如此袁稽,所有的家在請(qǐng)求最終都傳遞到最頂層的類加載器進(jìn)行處理勿璃;
  • 如果父加載器不存在了,那么嘗試判斷有沒(méi)有被啟動(dòng)類加載器加載推汽;
  • 如果的確沒(méi)有被夾在补疑,則再自己嘗試加載。

問(wèn)題:

  1. 為什么要有這么復(fù)雜的雙親委派機(jī)制歹撒?
    1. 如果沒(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)成流程圖咬清,即是:

image-20200105174045231

如山圖所以,總是先回嘗試讓父類加載器先加載,其次判斷啟動(dòng)類加載器是否已經(jīng)加載了枫振,最后才嘗試從當(dāng)前類加載器加載。轉(zhuǎn)換為更清晰的模型如下:

image-20200105195158889

雙親委派模型具有以下特點(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)。

如下圖:

image-20200105200625589

方法區(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提供了如下的自定義類加載器:

image-20200105205509075

現(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)的:

image-20200105213630571

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?

ClassLoader 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

一篇圖文徹底弄懂類加載器與雙親委派機(jī)制

版權(quán)聲明: 版權(quán)歸作者所有,未經(jīng)許可不得轉(zhuǎn)載秀仲,侵權(quán)必究融痛!聯(lián)系作者請(qǐng)加公眾號(hào)。


image
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載神僵,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者雁刷。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市保礼,隨后出現(xiàn)的幾起案子沛励,更是在濱河造成了極大的恐慌,老刑警劉巖氓英,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侯勉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡铝阐,警方通過(guò)查閱死者的電腦和手機(jī)址貌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人练对,你說(shuō)我怎么就攤上這事遍蟋。” “怎么了螟凭?”我有些...
    開(kāi)封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵虚青,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我螺男,道長(zhǎng)棒厘,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任下隧,我火速辦了婚禮奢人,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘淆院。我一直安慰自己何乎,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布土辩。 她就那樣靜靜地躺著支救,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拷淘。 梳的紋絲不亂的頭發(fā)上各墨,一...
    開(kāi)封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音启涯,去河邊找鬼欲主。 笑死,一個(gè)胖子當(dāng)著我的面吹牛逝嚎,可吹牛的內(nèi)容都是我干的扁瓢。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼补君,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼引几!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起挽铁,我...
    開(kāi)封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤伟桅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后叽掘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體楣铁,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年更扁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盖腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赫冬。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖溃列,靈堂內(nèi)的尸體忽然破棺而出劲厌,到底是詐尸還是另有隱情,我是刑警寧澤听隐,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布补鼻,位于F島的核電站,受9級(jí)特大地震影響雅任,放射性物質(zhì)發(fā)生泄漏风范。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一沪么、第九天 我趴在偏房一處隱蔽的房頂上張望乌企。 院中可真熱鬧,春花似錦成玫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至冗澈,卻和暖如春钦勘,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亚亲。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工彻采, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捌归。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓肛响,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惜索。 傳聞我的和親對(duì)象是個(gè)殘疾皇子特笋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345