關於Java類載入雙親委派機制的思考(附一道面試題)

Alexia(minmin)發表於2014-12-02

預定義類載入器和雙親委派機制

  1. JVM預定義的三種型別類載入器:

    • 啟動(Bootstrap)類載入器:是用原生程式碼實現的類裝入器,它負責將 <Java_Runtime_Home>/lib下面的類庫載入到記憶體中(比如rt.jar)。由於引導類載入器涉及到虛擬機器本地實現細節,開發者無法直接獲取到啟動類載入器的引用,所以不允許直接通過引用進行操作。
    • 標準擴充套件(Extension)類載入器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)實現的。它負責將< Java_Runtime_Home >/lib/ext或者由系統變數 java.ext.dir指定位置中的類庫載入到記憶體中。開發者可以直接使用標準擴充套件類載入器。
    • 系統(System)類載入器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)實現的。它負責將系統類路徑(CLASSPATH)中指定的類庫載入到記憶體中。開發者可以直接使用系統類載入器。

    除了以上列舉的三種類載入器,還有一種比較特殊的型別 — 執行緒上下文類載入器。

  2. 雙親委派機制描述
    某個特定的類載入器在接到載入類的請求時,首先將載入任務委託給父類載入器,依次遞迴,如果父類載入器可以完成類載入任務,就成功返回;只有父類載入器無法完成此載入任務時,才自己去載入。

 

幾點思考

  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類根本沒有機會得到載入。

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

 

相關文章