類加載器ClassLoader源碼解析

一、ClassLoader做用html

  • 類加載流程的"加載"階段是由類加載器完成的。

 

二、類加載器結構java

結構:BootstrapClassLoader(祖父)-->ExtClassLoader(爺爺)-->AppClassLoader(也稱爲SystemClassLoader)(爸爸)-->自定義類加載器(兒子)bootstrap

關係:看括號中的排位;彼此相鄰的兩個爲父子關係,前爲父,後爲子緩存

2.一、BootstrapClassLoaderapp

  • 下邊簡稱爲boot
  • C++編寫
  • 爲ExtClassLoader的父類,可是經過ExtClassLoader的getParent()獲取到的是null(在類加載器部分:null就是指boot)
  • 主要加載:E:\Java\jdk1.6\jre\lib\*.jar(最重要的就是:rt.jar)

2.二、ExtClassLoader:spa

  • 下邊簡稱爲ext
  • java編寫,位於sun.misc包下,該包在你導入源代碼的時候是沒有的,須要從新去下
  • 主要加載:E:\Java\jdk1.6\jre\lib\ext\*.jar(eg.dnsns.jar)

2.三、AppClassLoader:code

  • 下邊簡稱爲app
  • java編寫,位於sun.misc包下
  • 主要加載:類路徑下的jar

2.四、自定義類加載器:htm

  • 下邊簡稱爲custom
  • 本身編寫的類加載器,須要繼承ClassLoader類或URLClassLoader,並至少重寫其中的findClass(String name)方法,若想打破雙親委託機制,須要重寫loadClass方法
  • 主要加載:本身指定路徑的class文件

 

三、全盤負責機制對象

概念:假設ClassLoaderA要加載class B,可是B引用了class C,那麼ClassLoaderA先要加載C,再加載B,"全盤"的意思就是,加載B的類加載器A,也會加載B所引用的類blog

 

四、雙親委託機制

這也是類加載器加載一個類的整個過程。

過程:假設我如今從類路徑下加載一個類A,

1)那麼app會先查找是否加載過A,如有,直接返回;

2)若沒有,去ext檢查是否加載過A,如有,直接返回;

3)若沒有,去boot檢查是否加載過A,如有,直接返回;

4)若沒有,那就boot加載,若在E:\Java\jdk1.6\jre\lib\*.jar下找到了指定名稱的類,則加載,結束;

5)若沒找到,boot加載失敗;

6)ext開始加載,若在E:\Java\jdk1.6\jre\lib\ext\*.jar下找到了指定名稱的類,則加載,結束;

7)若沒找到,ext加載失敗;

8)app加載,若在類路徑下找到了指定名稱的類,則加載,結束;

9)若沒有找到,拋出異常ClassNotFoundException

注意:

  • 在上述過程當中的1)2)3)4)6)8)後邊,都要去判斷是否須要進行"解析"過程 ("解析"見 第四章 類加載機制
  • 類的加載過程只有向上的雙親委託,沒有向下的查詢和加載,假設是ext在E:\Java\jdk1.6\jre\lib\ext\*.jar下加載一個類,那麼整個查詢與加載的過程與app無關。
  • 假設A加載成功了,那麼該類就會緩存在當前的類加載器實例對象C中,key是(A,C)(其中A是類的全類名,C是加載A的類加載器對象實例),value是對應的java.lang.Class對象
  • 上述的1)2)3)都是從相應的類加載器實例對象的緩存中進行查找
  • 進行緩存的目的是爲了同一個類不被加載兩次
  • 使用(A,C)作key是爲了隔離類,假設如今有一個類加載器B也加載了A,key爲(A,B),則這兩個A是不一樣的A。這種狀況怎麼發生呢?
    • 假設有custom一、custom2兩個自定義類加載器,他們是兄弟關係,同時加載A,這就是有可能的了

總結:

  • 從底向上檢查是否加載過指定名稱的類;從頂向下加載該類。(在其中任何一個步驟成功以後,都會停止類加載過程)
  • 雙親委託的好處:假設本身編寫了一個java.lang.Object類,編譯後置於類路徑下,此時在系統中就有兩個Object類,一個是rt.jar的,一個是類路徑下的,在類加載的過程當中,當要按照全類名去加載Object類時,根據雙親委託,boot會加載rt.jar下的Object類,這是方法結束,即類路徑下的Object類就沒有加載了。這樣保證了系統中類不混亂。

 

五、源代碼

複製代碼
/**
     * 根據指定的binary name加載class。
     * 步驟:
     * 假設我如今從類路徑下加載一個類A,
     * 1)那麼app會先查找是否加載過A(findLoadedClass(name)),如有,直接返回;
     * 2)若沒有,去ext檢查是否加載過A(parent.loadClass(name, false)),如有,直接返回;
     * findBootstrapClassOrNull(name) 3)4)5)都是這個方法
     * 3)若沒有,去boot檢查是否加載過A,如有,直接返回;
     * 4)若沒有,那就boot加載,若在E:\Java\jdk1.6\jre\lib\*.jar下找到了指定名稱的類,則加載,結束;
     * 5)若沒找到,boot加載失敗;
     * findClass(name) 6)7)8)9)都是這個方法
     * 在findClass中調用了defineClass方法,該方法會生成當前類的java.lang.Class對象
     * 6)ext開始加載,若在E:\Java\jdk1.6\jre\lib\ext\*.jar下找到了指定名稱的類,則加載,結束;
     * 7)若沒找到,ext加載失敗;
     * 8)app加載,若在類路徑下找到了指定名稱的類,則加載,結束;
     * 9)若沒有找到,拋出異常ClassNotFoundException
     * 注意:在上述過程當中的1)2)3)4)6)8)後邊,都要去判斷是否須要進行"解析"過程
     */
    protected synchronized Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
        Class c = findLoadedClass(name);//檢查要加載的類是否是已經被加載了
        if (c == null) {//沒有被加載過
            try {
                if (parent != null) {
                    //若是父加載器不是boot,遞歸調用loadClass(name, false)
                    c = parent.loadClass(name, false);
                } else {//父加載器是boot
                    /*
                     * 返回一個由boot加載過的類;3)
                     * 若沒有,就去試着在E:\Java\jdk1.6\jre\lib\*.jar下查找 4)
                     * 若在bootstrap class loader的查找範圍內沒有查找到該類,則返回null 5)
                     */
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //父類加載器沒法完成加載請求
            }
            if (c == null) {
                //若是父類加載器未找到,再調用自己(這個自己包括ext和app)的findClass(name)來查找類
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
複製代碼

說明:

  • 該段代碼中引用的大部分方法實質上都是native方法
  • 其中findClass方法的類定義以下:
    複製代碼
    /**
         * 查找指定binary name的類
         * 該類應該被ClassLoader的實現類重寫
         */
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            throw new ClassNotFoundException(name);
        }
    複製代碼
    • 關於findClass能夠查看URLClassLoader.findClass(final String name),其中引用了defineClass方法,在該方法中將二進制字節流轉換爲了java.lang.Class對象。

     

    附:關於遞歸

    遞歸基於棧實現。

    上述的代碼若是不清楚遞歸的意義是看不清的。

    解釋:

    • app_loadClass()方法執行到ext_loadClass(),這時候對於app_loadClass()中剩餘的findClass()會在棧中向下壓;
    • 而後執行ext_loadClass(),當執行到findBootstrapClassOrNull(name),這時候ext_loadClass()中剩餘的findClass()也會從棧頂向下壓,此時ext_loadClass()_findClass()僅僅位於app_loadClass()_findClass()的上方;
    • 而後執行findBootstrapClassOrNull(name),當boot檢測事後而且執行完加載後而且沒成功,boot方法離開棧頂;
    • 而後執行此時棧頂的ext_loadClass()_findClass()
    • 而後執行此時棧頂的app_loadClass()_findClass()

    這樣,就完成了雙親委託機制。

    遞歸太煩了,實際開發中儘可能不要用!

相關文章
相關標籤/搜索