Java類載入器與雙親委派模型
1. 什麼是類載入機制?
程式碼編譯的結果從本地機器碼轉變成位元組碼,是儲存格式的一小步,卻是程式語言發展的一大步。
Java虛擬機器把描述類的資料從Class檔案載入進記憶體,並對資料進行校驗,轉換解析和初始化,最終形成可以唄虛擬機器直接使用的Java型別,這就是虛擬機器的類載入機制。
虛擬機器設計團隊把類載入階段中的“通過一個類的全限定名來獲取描述此類的二進位制位元組流”這個動作放到Java虛擬機器外部去實現,以便讓應用程式自己決定如何去獲取所需要的類。實現這動作的程式碼模組成為“類載入器”。
類與類載入器的關係
類載入器雖然只用於實現類的載入動作,但它在Java程式中起到的作用卻遠遠不限於類載入階段。對於任意一個類,都需要由載入他的類載入器和這個類本身一同確立其在Java虛擬機器中的唯一性
,每一個類載入器,都擁有一個獨立的類名稱空間。這句話可以表達的更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類載入器載入的前提下才有意義
,否則,即使這兩個類來自同一個Class檔案,被同一個虛擬機器載入,只要載入他們的類載入器不同,那這個兩個類就必定不相等。
啟動類載入器 Bootstrap ClassLoader:載入<JAVA_HOME>\lib目錄下核心庫
擴充套件類載入器 Extension ClassLoader:載入<JAVA_HOME>\lib\ext目錄下擴充套件包
應用程式類載入器 Application ClassLoader: 載入使用者路徑(classpath)上指定的類庫
雙親委派模型
雙親委派模型要求除頂層啟動類載入器外其餘類載入器都應該有自己的父類載入器;類載入器之間通過複用關係來複用父載入器的程式碼。
雙親委派模型工作工程:
1.當Application ClassLoader 收到一個類載入請求時,他首先不會自己去嘗試載入這個類,而是將這個請求委派給父類載入器Extension ClassLoader去完成。
2.當Extension ClassLoader收到一個類載入請求時,他首先也不會自己去嘗試載入這個類,而是將請求委派給父類載入器Bootstrap ClassLoader去完成。
3.如果Bootstrap ClassLoader載入失敗(在<JAVA_HOME>\lib中未找到所需類),就會讓Extension ClassLoader嘗試載入。
4.如果Extension ClassLoader也載入失敗,就會使用Application ClassLoader載入。
5.如果Application ClassLoader也載入失敗,就會使用自定義載入器去嘗試載入。
6.如果均載入失敗,就會丟擲ClassNotFoundException異常。
雙親委派模型的實現過程:
實現雙親委派模型的程式碼都集中在java.lang.ClassLoader的loadClass()方法中:
首先會檢查請求載入的類是否已經被載入過;
若沒有被載入過:
遞迴呼叫父類載入器的loadClass();
父類載入器為空後就使用啟動類載入器載入;
如果父類載入器和啟動類載入器均無法載入請求,則呼叫自身的載入功能。
雙親委派模型的優點:
Java類伴隨其類載入器具備了帶有優先順序的層次關係,確保了在各種載入環境的載入順序。
保證了執行的安全性,防止不可信類扮演可信任的類。
可以保證兩點:子載入器可以使用父類載入器已載入的類,但父類載入器無法使用子類載入器已載入的類;二是父載入器已載入過的類無法被子載入器再次載入。這樣就可以保證JVM安全性和穩定性。
雙親委任模型
-
從Java虛擬機器的角度來說,只存在兩種不同類載入器:一種是啟動類載入器(Bootstrap ClassLoader),這個類載入器使用C++語言實現(只限HotSpot),是虛擬機器自身的一部分;另一種就是所有其他的類載入器,這些類載入器都由Java語言實現,獨立於虛擬機器外部,並且全都繼承自抽象類
java.lang.ClassLoader
. -
從Java開發人員的角度來看,類載入還可以劃分的更細緻一些,絕大部分Java程式設計師都會使用以下3種系統提供的類載入器:
- 啟動類載入器(Bootstrap ClassLoader):這個類載入器複雜將存放在 JAVA_HOME/lib 目錄中的,或者被-Xbootclasspath 引數所指定的路徑種的,並且是虛擬機器識別的(僅按照檔名識別,如rt.jar,名字不符合的類庫即使放在lib目錄下也不會過載)。
- 擴充套件類載入器(Extension ClassLoader):這個類載入器由sun.misc.Launcher$ExtClassLoader實現,它負責夾雜JAVA_HOME/lib/ext 目錄下的,或者被java.ext.dirs 系統變數所指定的路徑種的所有類庫。開發者可以直接使用擴充套件類載入器。
- 應用程式類載入器(Application ClassLoader):這個類載入器由sun.misc.Launcher$AppClassLoader 實現。由於這個類載入器是ClassLoader 種的getSystemClassLoader方法的返回值,所以也成為系統類載入器。它負責載入使用者類路徑(ClassPath)上所指定的類庫。開發者可以直接使用這個類載入器,如果應用中沒有定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。
這些類載入器之間的關係一般如下圖所示:
為什麼需要雙親委派模型?
為什麼需要雙親委派模型呢?假設沒有雙親委派模型,試想一個場景:
黑客自定義一個
java.lang.String
類,該String
類具有系統的String
類一樣的功能,只是在某個函式稍作修改。比如equals
函式,這個函式經常使用,如果在這這個函式中,黑客加入一些“病毒程式碼”。並且通過自定義類載入器加入到JVM
中。此時,如果沒有雙親委派模型,那麼JVM
就可能誤以為黑客自定義的java.lang.String
類是系統的String
類,導致“病毒程式碼”被執行。
而有了雙親委派模型,黑客自定義的java.lang.String
類永遠都不會被載入進記憶體。因為首先是最頂端的類載入器載入系統的java.lang.String
類,最終自定義的類載入器無法載入java.lang.String
類。
如果沒有使用雙親委派模型,由各個類載入器自行載入的話,如果使用者自己編寫了一個稱為java.lang.Object的類,並放在程式的ClassPath中,那系統將會出現多個不同的Object類, Java型別體系中最基礎的行為就無法保證。應用程式也將會變得一片混亂。
或許你會想,我在自定義的類載入器裡面強制載入自定義的java.lang.String
類,不去通過呼叫父載入器不就好了嗎?確實,這樣是可行。但是,在JVM
中,判斷一個物件是否是某個型別時,如果該物件的實際型別與待比較的型別的類載入器不同,那麼會返回false。
舉個簡單例子:
ClassLoader1
、ClassLoader2
都載入java.lang.String
類,對應Class1、Class2物件。那麼Class1
物件不屬於ClassLoad2
物件載入的java.lang.String
型別。