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

前言

之前詳細介紹了Java類的整個加載過程(

類加載機制)。雖然,篇幅較長,但是也不要被內容嚇到了,其實每個階段都可以用一句話來概括。

1)加載:查找並加載類的二進制字節流數據。

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

3)準備:為類的靜態變量分配內存,並設置默認初始值。

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

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

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

類加載器

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

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

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

簡單來說,類加載器的作用就是去加載class類的二進制字節流的。

類加載器有以下三種:

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

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

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

3)應用程序類加載器(Application ClassLoader)。這個加載器加載的是你的項目工程的ClassPath目錄下的類庫。如果用戶沒有自定義自己的類加載器,這個就是程序默認的類加載器。

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

我們也可以通過代碼把類加載器打印出來:

<code>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());
    }
}/<code>

打印結果:

<code>null
sun.misc.Launcher$AppClassLoader@58644d46
sun.misc.Launcher$ExtClassLoader@6d6f6e28
null/<code>

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

雙親委派機制

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

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

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

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

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

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

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

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

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

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

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

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


分享到:


相關文章: