1. 程式人生 > >(二)類載入器與雙親委派模型

(二)類載入器與雙親委派模型

類載入機制的第一個階段載入做的工作有:

1、通過一個類的全限定名(包名與類名)來獲取定義此類的二進位制位元組流(Class檔案)。而獲取的方式,可以通過jar包、war包、網路中獲取、JSP檔案生成等方式。

2、將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。這裡只是轉化了資料結構,並未合併資料。(方法區就是用來存放已被載入的類資訊,常量,靜態變數,編譯後的程式碼的執行時記憶體區域)

3、在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料的訪問入口。這個Class物件並沒有規定是在Java堆記憶體中,它比較特殊,雖為物件,但存放在方法區中。

其中,實現第一個工作的程式碼塊就被稱為“類載入器”。

類載入器的作用不僅僅是實現類的載入,它還與類的的“相等”判定有關,關係著Java“相等”判定方法的返回結果,只有在滿足如下三個類“相等”判定條件,才能判定兩個類相等。

1、兩個類來自同一個Class檔案

2、兩個類是由同一個虛擬機器載入

3、兩個類是由同一個類載入器載入

Java“相等”判定相關方法:

1、判斷兩個例項物件的引用是否指向記憶體中同一個例項物件,使用 Class物件的equals()方法,obj1.equals(obj2); 2、判斷例項物件是否為某個類、介面或其子類、子介面的例項物件,使用Class物件的isInstance()方法,class.isInstance(obj);

3、判斷例項物件是否為某個類、介面的例項,使用instanceof關鍵字,obj instanceof class; 4、判斷一個類是否為另一個類本身或其子類、子介面,可以使用Class物件的isAssignableFrom()方法,class1.isAssignableFrom(class2)。

JVM類載入器分類詳解:

1、Bootstrap ClassLoader:啟動類載入器,也叫根類載入器,它負責載入Java的核心類庫,載入如(%JAVA_HOME%/lib)目錄下的rt.jar(包含System、String這樣的核心類)這樣的核心類庫。根類載入器非常特殊,它不是java.lang.ClassLoader的子類,它是JVM自身內部由C/C++實現的,並不是Java實現的。

2、Extension ClassLoader:擴充套件類載入器,它負責載入擴充套件目錄(%JAVA_HOME%/jre/lib/ext)下的jar包,使用者可以把自己開發的類打包成jar包放在這個目錄下即可擴充套件核心類以外的新功能。

3、System ClassLoader\APP ClassLoader:系統類載入器或稱為應用程式類載入器,是載入CLASSPATH環境變數所指定的jar包與類路徑。一般來說,使用者自定義的類就是由APP ClassLoader載入的。

各種類載入器間關係:以組合關係複用父類載入器的父子關係,注意,這裡的父子關係並不是以繼承關係實現的。

//驗證類載入器與類載入器間的父子關係
	public static void main(String[] args) throws Exception{
		//獲取系統/應用類載入器
		ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
		System.out.println("系統/應用類載入器:" + appClassLoader);
		//獲取系統/應用類載入器的父類載入器,得到擴充套件類載入器
		ClassLoader extcClassLoader = appClassLoader.getParent();
		System.out.println("擴充套件類載入器" + extcClassLoader);
		System.out.println("擴充套件類載入器的載入路徑:" + System.getProperty("java.ext.dirs"));
		//獲取擴充套件類載入器的父載入器,但因根類載入器並不是用Java實現的所以不能獲取
		System.out.println("擴充套件類的父類載入器:" + extcClassLoader.getParent());
	}
}

 類載入器的雙親委派載入機制(重點):當一個類收到了類載入請求,他首先不會嘗試自己去載入這個類,而是把這個請求委派給父類去完成,每一個層次類載入器都是如此,因此所有的載入請求都應該傳送到啟動類載入其中,只有當父類載入器反饋自己無法完成這個請求的時候(在它的載入路徑下沒有找到所需載入的Class),子類載入器才會嘗試自己去載入。

這個過程如下圖示號過程所示:

雙親委派模型的原始碼實現:

主要體現在ClassLoader的loadClass()方法中,思路很簡單:先檢查是否已經被載入過,若沒有載入則呼叫父類載入器的loadClass()方法,若父類載入器為空則預設使用啟動類載入器作為父類載入器。如果父類載入器載入失敗,丟擲ClassNotFoundException異常後,呼叫自己的findClass()方法進行載入。

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
protected synchronized Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
    {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            c = findBootstrapClassOrNull(name);
        }
        } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}

下面看一個簡單的雙親委派模型程式碼例項驗證

public class ClassLoaderTest {
	public static void main(String[] args){
		//輸出ClassLoaderText的類載入器名稱
		System.out.println("ClassLoaderText類的載入器的名稱:"+ClassLoaderTest.class.getClassLoader().getClass().getName());
		System.out.println("System類的載入器的名稱:"+System.class.getClassLoader());
		System.out.println("List類的載入器的名稱:"+List.class.getClassLoader());
 
		ClassLoader cl = ClassLoaderTest.class.getClassLoader();
		while(cl != null){
				System.out.print(cl.getClass().getName()+"->");
				cl = cl.getParent();
		}
		System.out.println(cl);
}

輸出結果為:

解釋一下:

1、ClassLoaderTest類是使用者定義的類,位於CLASSPATH下,由系統/應用程式類載入器載入。

2、System類與List類都屬於Java核心類,由祖先類啟動類載入器載入,而啟動類載入器是在JVM內部通過C/C++實現的,並不是Java,自然也就不能繼承ClassLoader類,自然就不能輸出其名稱。

3、而箭頭項代表的就是類載入的流程,層級委託,從祖先類載入器開始,直到系統/應用程式類載入器處才被載入。

那麼我們做個測試,把類打成jar包,拷貝入%JAVA_HOME%/jre/lib/ext目錄下,再次執行ClassLoaderTest類

      解釋一下,因為類的Jar包放到了ExtClassLoader的載入目錄下,所以在根目錄找不到相應類後,在ExtClassLoader處就完成了類載入,而忽略了APPClassLoader階段。