ClassLoader之淺談雙親委派模型

Java的ClassLoader一直是一個很神奇的東西,很多黑科技都離不開它的存在,想要成爲高級java工程師,它也基本是面試必問的,之前想要學習一直不得法,最近琢磨出一點味道了,分享給大家。

ClassLoader爲何存在?

java源文件編譯後都是以字節碼存在.class文件中,想要使用這個class,我們必須將.class文件中的字節碼翻譯成內存中對應的結構,從而被jvm虛擬機使用,classloader也就是起這個翻譯作用。換句話說,所有類在被jvm使用前,都要經過classloader,以面向切面編程的思想來說,這裏就能做很多手腳了。

ClassLoader種類

雖說大家都是java類,但java類也是分三六九等的,不是同一階級的java類自然不能使用同一種classloader去加載,總的來說分爲四類:

  • 啓動類加載器(Bootstrap ClassLoader)
    加載放在<JAVA_HOME>/lib下的或被-Xbootclasspath參數所指定的路勁中一些事先定義好的類庫,比如rt.jar。注意,即使你放置一個自己的my.jar到這些路勁下也是不會被加載的。
  • 擴展類加載器(Extension ClassLoader)
    加載<JAVA_HOME>/lib/ext下的類庫,或者被java.ext.dirs系統變量指定位置的類庫。
  • 應用程序類加載器
    加載開發者自己定義的classpath上的類庫,比如我們自己編寫的java代碼編譯成的class文件就是由這個類加載器加載的。
  • 自定義類加載器
    有時候,應用程序類加載器不能滿足我們的要求,會自己定義一些類加載器。但其實這應該也算一種應用程序類加載器。

雙親委派模型的意義

繼續往下閱讀之前,我們明確一個知識點:java判斷一個兩個對象是否相等的前提是在同一個ClassLoader加載的前提下,比較兩個不同classloader加載的對象沒有意義
從上面我們看到不同類型的類加載器一般只加載固定目錄的類庫,但是,類加載器是可以自定義的,我非要在自定義的類加載器中加載<JAVA_HOME>/lib下的類又會怎樣呢?如果非要這麼做的話,java基本的euqal等方法就成立了,整個java體系也就亂了,所以爲了解決這個問題,雙親委派模型就推出來了。
先來一張經典圖片


所謂的雙親委派模型就是在加載一個類時,先將這個類交給父級加載器加載,如果父級加載器無法加載再由自己加載
這樣,我們可以保證Object類永遠都是由啓動類加載器加載,也就不存在equals方法不能正常使用的問題了。

如何實現雙親委派模型

既然雙親委派模型如此重要,那麼我們平時自定義classloader時肯定也是要遵循這個模型的,首先我們看下默認的是怎麼實現的,代碼在ClassLoader類的loadClass()方法中:

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.
                    //父級加載器無法加載則自己加載,核心就是findClass方法
                    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;
        }
    }

可以看到,loadClass()方法中實現了標準的雙親委派模型,如果我們不想打破這個模型,那麼我們重寫findClass()方法就好了。

雙親委派模型的破壞

無疑雙親委派模型是一種非常合適的類加載模型,但萬事沒有絕對,這個模型也有被破壞的時候。

JDK1.2之前的破壞

雙親委派模型是jdk1.2之後才被引入,而classloader是jdk1.0發佈的,那麼這段時間大家自定義classloader都是重寫loadClass()方法,自然不存在遵守雙親委派模型的情況了。

自身缺陷導致的被破壞

事實上,雙親委派模型有一個很大的缺陷,那就是父級加載器是永遠不可能加載子級加載器路徑中的類的,但我們確實是有這種需求的。最經典的就是java本身會對一些操作定義一些標準的接口,比如sql的接口,log的接口等等,這些接口的具體實現由各大廠商實現,比如mysql有自己Driver實現,oracle也有自己的driver實現。java如果想做到在父級路徑的類中加載這些廠商提供的類必然會出現上級類加載器加載下級類加載器路徑中類的情況,這在雙親委派模型中是做不到的。
對於這種情況的具體理解大家可以看下我的這篇文章,有詳細的解釋:
以JDBC爲例談雙親委派模型的破壞

追求“動態化”,“熱部署”導致的破壞

java爲了追求熱部署,熱修復,推出了一套osgi規範,這套規範中ClassLoader不再遵循雙親委派模型,而是變成了一個網狀結構,關於osgi規範比價複雜,目前應用也不多,不是幾篇文章能講明白的,大家有興趣可以去看這本書 《深入理解OSGI:Equinox原理、應用與最佳實踐》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章