Java自定義類加載器與雙親委派模型

1. 雙親委派模型

關於雙親委派模型,網上的資料有很多。我這裏只簡單的描述一下,就當是複習。

1.1 什麼是雙親委派模型?

首先,先要知道什麼是類加載器。簡單說,類加載器就是根據指定全限定名稱將class文件加載到JVM內存,轉爲Class對象。如果站在JVM的角度來看,只存在兩種類加載器:

  • 啓動類加載器(Bootstrap ClassLoader):由C++語言實現(針對HotSpot),負責將存放在<JAVA_HOME>\lib目錄或-Xbootclasspath參數指定的路徑中的類庫加載到內存中。

  • 其他類加載器:由Java語言實現,繼承自抽象類ClassLoader。如:

    • 擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\lib\ext目錄或java.ext.dirs系統變量指定的路徑中的所有類庫。
    • 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,我們可以直接使用這個類加載器。一般情況,如果我們沒有自定義類加載器默認就是用這個加載器。

雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器完成。每個類加載器都是如此,只有當父加載器在自己的搜索範圍內找不到指定的類時(即ClassNotFoundException),子加載器纔會嘗試自己去加載。雙親委派模型如下:ç±»å è½½å¨çå亲å§æ´¾æ¨¡å

 

 

 

雙親委派模型過程

某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,如果父類加載器可以完成類加載任務,就成功返回;只有父類加載器無法完成此加載任務時,才自己去加載。

使用雙親委派模型的好處在於Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係。例如類java.lang.Object,它存在在rt.jar中,無論哪一個類加載器要加載這個類,最終都是委派給處於模型最頂端的Bootstrap ClassLoader進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。相反,如果沒有雙親委派模型而是由各個類加載器自行加載的話,如果用戶編寫了一個java.lang.Object的同名類並放在ClassPath中,那系統中將會出現多個不同的Object類,程序將混亂。因此,如果開發者嘗試編寫一個與rt.jar類庫中重名的Java類,可以正常編譯,但是永遠無法被加載運行。

雙親委派模型的系統實現

在java.lang.ClassLoader的loadClass()方法中,先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()方法,若父加載器爲空則默認使用啓動類加載器作爲父加載器。如果父加載失敗,則拋出ClassNotFoundException異常後,再調用自己的findClass()方法進行加載。

protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
    //check the class has been loaded or not
    Class c = findLoadedClass(name);
    if(c == null){
        try{
            if(parent != null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //if throws the exception ,the father can not complete the load
        }
        if(c == null){
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}

從上面代碼可以明顯看出,loadClass(String, boolean)函數即實現了雙親委派模型!整個大致過程如下:

  1. 首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接返回。
  2. 如果此類沒有加載過,那麼,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即調用parent.loadClass(name, false);).或者是調用bootstrap類加載器來加載。
  3. 如果父加載器及bootstrap類加載器都沒有找到指定的類,那麼調用當前類加載器的findClass方法來完成類加載。

話句話說,如果自定義類加載器,就必須重寫findClass方法!

 findclass方法

findClass的默認實現如下:

protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
}

 

可以看出,抽象類ClassLoaderfindClass函數默認是拋出異常的。而前面我們知道,loadClass在父加載器無法加載類的時候,就會調用我們自定義的類加載器中的findeClass函數,因此我們必須要在loadClass這個函數裏面實現將一個指定類名稱轉換爲Class對象.

如果是是讀取一個指定的名稱的類爲字節數組的話,這很好辦。但是如何將字節數組轉爲Class對象呢?很簡單,Java提供了defineClass方法,通過這個方法,就可以把一個字節數組轉爲Class對象啦~

defineClass方法

defineClass主要的功能是:

將一個字節數組轉爲Class對象,這個字節數組是class文件讀取後最終的字節數組。如,假設class文件是加密過的,則需要解密後作爲形參傳入defineClass函數。

defineClass默認實現如下:

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError  {
        return defineClass(name, b, off, len, null);
}

我們看一下整個函數的調用過程:

最後

整理的一點思考和麪試題:

  1. Java虛擬機的第一個類加載器是Bootstrap,這個加載器很特殊,它不是Java類,因此它不需要被別人加載,它嵌套在Java虛擬機內核裏面,也就是JVM啓動的時候Bootstrap就已經啓動,它是用C++寫的二進制代碼(不是字節碼),它可以去加載別的類。

    這也是我們在測試時爲什麼發現System.class.getClassLoader()結果爲null的原因,這並不表示System這個類沒有類加載器,而是它的加載器比較特殊,是BootstrapClassLoader,由於它不是Java類,因此獲得它的引用肯定返回null。

  2. 委託機制具體含義 
    當Java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?

    • 首先當前線程的類加載器去加載線程中的第一個類(假設爲類A)。 
      注:當前線程的類加載器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設置類加載器。
    • 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B。
    • 還可以直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。
  3. 委託機制的意義 — 防止內存中出現多份同樣的字節碼 
    比如兩個類A和類B都要加載System類:

    • 如果不用委託而是自己加載自己的,那麼類A就會加載一份System字節碼,然後類B又會加載一份System字節碼,這樣內存中就出現了兩份System字節碼。
    • 如果使用委託機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這裏的System就能在Bootstrap中找到然後加載,如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發現已經加載過了System那麼直接返回內存中的System即可而不需要重新加載,這樣內存中就只有一份System的字節碼了。

  問:能不能自己寫個類叫java.lang.System

  答案:通常不可以,但可以採取另類方法達到這個需求。 
 解釋:爲了不讓我們寫System類,類加載採用委託機制,這樣可以保證爸爸們優先,爸爸們能找到的類,兒子就沒有機會加      載。而System類是Bootstrap加載器加載的,就算自己重寫,也總是使用Java系統提供的System,自己寫的System類根本沒有機會得到加載。

但是,我們可以自己定義一個類加載器來達到這個目的,爲了避免雙親委託機制,這個類加載器也必須是特殊的。由於系統自帶的三個類加載器都加載特定目錄下的類,如果我們自己的類加載器放在一個特殊的目錄,那麼系統的加載器就無法加載,也就是最終還是由我們自己的加載器加載。

 

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