1. 程式人生 > >Java類載入器和雙親委派機制

Java類載入器和雙親委派機制

前言

之前詳細介紹了Java類的整個載入過程(類載入機制詳解)。雖然,篇幅較長,但是也不要被內容嚇到了,其實每個階段都可以用一句話來概括。

1)載入:查詢並載入類的二進位制位元組流資料。

2)驗證:保證被載入的類的正確性。

3)準備:為類的靜態變數分配記憶體,並設定預設初始值。

4)解析:把類中的符號引用轉換為直接引用。

5)初始化:為類的靜態變數賦予正確的初始值。

當然,要想掌握類載入機制,還是需要去深入研究的。(好吧,說了一句正確的廢話)因為其中,有很多知識點也是面試中常問的。比如,我之前去面試的時候,面試官就問到了一個和類初始化相關的問題。就是給一段程式碼,有父子類關係,父子類中包含靜態程式碼塊、構造程式碼塊、普通程式碼塊、建構函式等,然後讓判斷程式碼最終的執行順序。(可自行思考一下,具體內容細節暫時不做擴充套件)

類載入器

終於來到了本文的主題 —— 類載入器和雙親委派機制。

在《深入理解Java虛擬機器》中,對於類載入器的定義是這樣的:

虛擬機器設計團隊把類載入階段中的“通過一個類的許可權定名來獲取描述此類的二進位制位元組流”這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何去獲取所需要的類。實現這個動作的程式碼模組稱為“類載入器”。

簡單來說,類載入器的作用就是去載入class類的二進位制位元組流的。

類載入器有以下三種:

1)啟動類載入器(Bootstrap ClassLoader),或者叫根載入器。這個類載入器主要是去載入你在本機配置的環境變數 Java_Home/jre/lib 目錄下的核心API,如rt.jar

2)擴充套件類載入器(Extension ClassLoader)。這個載入器負責載入 Java_Home/jre/lib/ext 目錄下的所有jar包。

3)應用程式類載入器(Application ClassLoader)。這個載入器載入的是你的專案工程的ClassPath目錄下的類庫。如果使用者沒有自定義自己的類載入器,這個就是程式預設的類載入器。

另外,如果有需要的話,使用者也可以自定義自己的類載入器(去繼承ClassLoader類)。

我們也可以通過程式碼把類載入器打印出來:

public class TestClassLoader {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(obj.getClass().getClassLoader());

        TestClassLoader t = new TestClassLoader();
        System.out.println(t.getClass().getClassLoader());
        System.out.println(t.getClass().getClassLoader().getParent());
        System.out.println(t.getClass().getClassLoader().getParent().getParent());
    }
}

列印結果:

null
sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@6d6f6e28
null

注意,上面第一行和第四行的null此處可不是空的意思,它代表的是啟動類載入器。因為啟動類載入器是用C++程式碼來實現的,嚴格來說不屬於Java類,所以Java程式碼訪問不到,故返回null。第二行是應用程式類載入器,第三行是擴充套件類載入器。

雙親委派機制

在介紹雙親委派機制之前,先觀察一下以下程式碼能否正確執行:

//自己定義的一個 java.lang包
package java.lang;

public class String {
    public static void main(String[] args) {
        String s = new String();
        System.out.println(s);
    }
}

以上程式碼,編譯沒有任何問題,但是執行時,卻報錯:

為什麼提示在java.lang.String類中找不到main方法呢,我這明明不是定義了嗎?其實,問題的關鍵就在於類載入遵循雙親委派機制。

類載入器有以下這樣的層次關係:

當一個類在載入的時候,都會先委派它的父載入器去載入,這樣一層層的向上委派,直到最頂層的啟動類載入器。如果頂層無法載入(即找不到對應的類),就會一層層的向下查詢,直到找到為止。 這就是類的雙親委派機制。

這樣做有什麼好處呢?這就相當於維護了一個有優先順序的層級關係,即總是從最頂層的父載入器開始載入。這就如同,你工作中遇到了問題需要向上反饋,比如先反饋給小組長,然後小組長反饋給上級經理,最後經理反饋給boss。然後boss感覺這問題太簡單了不需要他親自出手,讓經理自己解決吧,然後經理又向下交給小組長。小組長一看,這問題不算難,人也比較熱心,於是就幫你把問題解決了。(可能例子不是太恰當哈,意思理解即可)

到此,我們就明白了為什麼上邊的程式碼會報錯。因為雙親委派機制的存在,去載入我們自己定義的“java.lang.String”類的時候,會最終委派到頂層的啟動類載入器,然後找到了rt.jar包下的“java.lang.String”。找到之後,就直接載入rt.jar包的String類(也就是我們經常使用的那個字串類),不再去向下查詢,也就載入不了我們自定義的String類了。由於,rt.jar包下的String類中確實沒有main方法,所以才會有以上的報錯資訊。

我們可以試想一下,如果沒有雙親委派機制的存在,那我這段程式碼是不是就可以執行成功了。如果這樣的話,豈不是說明我可以隨意覆蓋rt.jar包中的類(如String,Integer類等)。這樣的話將會使程式陷入混亂,Java核心包中的類的安全也無法保證