Java類加載機制-筆記4(雙親委派機制)

雙親委派機制

需求: 在默認情況下,一個限定名的類只會被一個類加載器加載并解析使用,這樣在程序中妙蔗,他就是不唯一的,不會產生歧義疆瑰。

如何實現(xiàn)這種需求眉反?
JVM的開發(fā)者引入了雙親委派模型,這個名字聽上去很高大上穆役,其實邏輯非常簡單寸五,我們通過這張圖來理解一下:

雙親委派模型

解釋一下這張圖,也就是說:在被動的情況下孵睬,當一個類收到加載請求播歼,他不會首先自己去加載,而是傳遞給自己的父親加載器掰读,這樣所有的類都會傳遞到最上層的Bootstrap ClassLoader 秘狞,只有父親加載器無法完成加載,那么此時兒子加載器才會自己去嘗試加載蹈集,什么叫無法加載烁试?就是根據(jù)類的限定名類加載器沒有在自己負責的加載路徑中找到該類,這里注意:父親加載器拢肆、兒子加載器减响,不同于父加載器靖诗,子加載器,因為上圖中這些箭頭并不表示繼承關系支示,而是一種邏輯關系刊橘,實際上是通過組合的方式來實現(xiàn)的,這也是很多博客上沒有寫清楚的容易誤導人的一點颂鸿。接下來我們就通過源碼來看下雙親委派機制具體是怎么實現(xiàn)的促绵。

代碼很簡單(取自java.lang.ClassLoader):

    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 {
                       // parent == null 代表 parent為bootstrap classloader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 說明parent加載不了,當前l(fā)oader嘗試 findclass
                    // 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;
        }
    }

首先檢查該類是否已經(jīng)被加載過嘴纺,如果沒有败晴,則開啟加載流程,如果有栽渴,則直接讀取緩存尖坤。parent變量代表了當前classloader的父親加載器,這里就體現(xiàn)了闲擦,不是通過繼承而是通過組合的方式實現(xiàn)類加載器之間的 父子關系慢味。如果parent==null,約定parent是bootstrap classloader 佛致,因為最開始我們也說過贮缕,bootstrap classloader 是由JVM內部實現(xiàn)的,沒有辦法被程序引用俺榆,所以這里就約定為null感昼,當parent為null,就調用findBootstrapClassOrNull這個方法罐脊,讓bootstrap classloader 嘗試進行加載定嗓,如果parent不為null,那么就讓parent根據(jù)限定名去嘗試加載該類萍桌,并返回class對象宵溅。如果返回的class對象為null,那么就說明parent沒有能力去加載這個類上炎,那么就調用findClass恃逻,findClass表示如何去尋找該限定名的class需要各個類加載器自己實現(xiàn),比如Extension ClassLoader 和Application ClassLoader都使用了這段邏輯來實現(xiàn)自己的findClass藕施。
(取自java.net.URLClassLoader)

    protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

這里可以看到寇损,通過將類的限定名轉化為文件path,再通過ucp這個對象去進行尋找裳食,找到文件資源后矛市,再調用defineClass去進行類加載的后續(xù)流程,
defineClass 方法(java.net.URLClassLoader)

    protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        int len = b.remaining();

        // Use byte[] if not a direct ByteBufer:
        if (!b.isDirect()) {
            if (b.hasArray()) {
                return defineClass(name, b.array(),
                                   b.position() + b.arrayOffset(), len,
                                   protectionDomain);
            } else {
                // no array, or read-only array
                byte[] tb = new byte[len];
                b.get(tb);  // get bytes out of byte buffer.
                return defineClass(name, tb, 0, len, protectionDomain);
            }
        }

        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass2(name, b, b.position(), len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

defineClass 方法是由java.lang.ClassLoader中一個被final修飾的方法诲祸,意味著獲取到class二進制流以后呢浊吏,最終將會由java.lang.classloader 來進行后續(xù)的操作而昨,因為它是被final修飾的,即不允許被外部重寫找田,這符合了我們最開始所說的類加載過程中除了讀取二進制流的操作外剩余邏輯都是有JVM內部實現(xiàn)的設計歌憨,這就是雙親委派模型。
我們在看一下上回提到的兩個問題:

問題:
1.不同的類加載器午阵,除了讀取二進制流的動作和范圍不一樣躺孝,后續(xù)的加載器邏輯是否也不一樣?
2.遇到限定名一樣的類底桂,這么多類加載器會不會產生混亂?

解答:
1.我們認為除了Bootstrap ClassLoader惧眠,所有的非Bootstrap ClassLoader都繼承了java.lang.ClassLoader籽懦,都由這個類的defineClass進行后續(xù)處理。
2.越核心的類庫越被上層的類加載器加載氛魁,而某限定名的類一旦被加載過了暮顺,被動情況下,就不會再加載相同限定名的類秀存。這樣捶码,就能夠有效避免混亂。

破壞雙親委派

第一次破壞雙親委派

但是雙親委派模型或链,并不是一個具有強約束力的模型惫恼。因為它存在設計缺陷,在大部分被動情況下澳盐,也就是上層開發(fā)者正常寫代碼祈纯,沒有騷操作的情況下,他是生效并且好用的叼耙。在一些情況下腕窥,雙親委派模型可以被主動破壞,細心的同學可能已經(jīng)發(fā)現(xiàn)了筛婉,我上面自己寫的用于被證明類加載器存在命名空間的demo就是一次對雙親委派模型的破壞簇爆,可以看到,這里自定義的類加載器直接重寫了java.lang.ClassLoader的loadClass方法爽撒,而雙親委派的邏輯就是存在于這個方法內的入蛆,那么我的這個重寫就代表了對原有雙親委派邏輯的破壞,所以就出現(xiàn)了一個限定名對應兩種不同class的情況匆浙,
需要提出的 是安寺,除非是有特殊的業(yè)務場景,一般來說不要去主動破壞雙親委派模型首尼,那么JVM推薦并希望開發(fā)者遵循雙親委派模型挑庶,那么為什么不把loadClass方法像defineClass方法一樣設定成final來修飾言秸?那這樣的情況,就沒有辦法去重寫loadClass方法迎捺,也就代表著上層開發(fā)者盡量遵循雙親委派的邏輯了举畸。
因為這是JVM開發(fā)者必須面對,但是無法解決的問題凳枝,java.lang.ClassLoader 的loadClass方法抄沮,在java很早的版本就有了,而雙親委派模型是在JDK1.2引入的特性岖瑰,Java是向下兼容的叛买,也就是說,引入雙親委派機制時蹋订,世界上已經(jīng)存在了很多像上面一樣的代碼率挣。JVM既然無法拒絕支持,只能默默接受露戒,一點補救措施呢椒功,就是在JDK1.2版本后引入了findClass方法,推薦用戶去重寫該方法而不是直接重寫loadClass方法智什,這樣就毅然能符合雙親委派动漾,這是史上第一次破壞雙親委派。

第二次破壞雙親委派

我們舉個例子:比如JDK想要提供操作數(shù)據(jù)庫的功能荠锭。
那么數(shù)據(jù)庫有很多種旱眯,并且隨著時間的推移,將會出現(xiàn)更多的品種的數(shù)據(jù)庫节沦,比較合理的方式是键思,JDK提供一組規(guī)范、一組接口甫贯,各個不同的數(shù)據(jù)庫廠商按照這個接口去自己實現(xiàn)自己類庫吼鳞。
這里就問題就出現(xiàn)了:
對JDK代碼包中的加載肯定使用了上層的類加載器,比如說bootstrapClassLoader 但當你去調用JDK 中的接口時叫搁,接口所在的類將會引起第三方類庫的加載這就不符合自下而上的委派加載順序了赔桌,而是出現(xiàn)了上層類加載器放下身段去調用下層類加載器的情況,這就產生了對雙親委派模型的破壞渴逻。

這就是Java的SPI
我們可以把SPI理解成一種服務發(fā)現(xiàn)機制疾党,各大廠商的服務注冊到JDK提供的接口上,上層在調用JDK的接口時惨奕,JDBC是SPI的其中一種功能雪位,在上面的例子中我們在JDBC上注冊了mysql Driver,h2 Driver這兩種服務梨撞,那么這里SPI究竟是如何對雙親委派進行破壞的呢雹洗,我們看一下DriverManager的源碼來簡單看一下:
可以看到DriverManager會主動的對第三方Driver進行加載香罐,掃描到所有注冊為java.sql.driver類型的第三方類就使用serviceLoader去進行加載,而serviceLoader內部使用了當前線程context中的類加載器时肿,一般線程context中的類加載器默認為application ClassLoader 庇茫,所以這些第三方類也就能夠被正常加載了,所以再結合這些輸出內容螃成。


第三次破壞雙親委派

隨著人們對模塊化的追求旦签,希望在程序運行時,能夠動態(tài)的對部分組件代碼進行替換寸宏,這就是所謂的熱替換宁炫、熱部署,想想也能夠大致猜到击吱,這里又將會出現(xiàn)很多的自由的類加載操作淋淀,所以又將是一次對雙親委派模型的踐踏。

問題: 能不能自己寫一個限定名為java.lang.String的類覆醇,并在程序中調用它?

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末炭臭,一起剝皮案震驚了整個濱河市永脓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鞋仍,老刑警劉巖常摧,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異威创,居然都是意外死亡落午,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門肚豺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來溃斋,“玉大人,你說我怎么就攤上這事吸申」=伲” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵截碴,是天一觀的道長梳侨。 經(jīng)常有香客問我,道長日丹,這世上最難降的妖魔是什么走哺? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮哲虾,結果婚禮上丙躏,老公的妹妹穿的比我還像新娘择示。我一直安慰自己,他們只是感情好彼哼,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布对妄。 她就那樣靜靜地躺著,像睡著了一般敢朱。 火紅的嫁衣襯著肌膚如雪剪菱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天拴签,我揣著相機與錄音孝常,去河邊找鬼。 笑死蚓哩,一個胖子當著我的面吹牛构灸,可吹牛的內容都是我干的。 我是一名探鬼主播岸梨,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼喜颁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了曹阔?” 一聲冷哼從身側響起半开,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赃份,沒想到半個月后寂拆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡抓韩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年纠永,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谒拴。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡尝江,死狀恐怖,靈堂內的尸體忽然破棺而出彪薛,到底是詐尸還是另有隱情茂装,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布善延,位于F島的核電站少态,受9級特大地震影響,放射性物質發(fā)生泄漏易遣。R本人自食惡果不足惜彼妻,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侨歉,春花似錦屋摇、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至牵舵,卻和暖如春柒啤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畸颅。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工担巩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人没炒。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓涛癌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親送火。 傳聞我的和親對象是個殘疾皇子拳话,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容