類加載器及父親委託機制

一、類加載器

1、在Java中,有兩種類型的類加載器,分別是JVM自帶的類加載器和用戶自定義的類加載器。

2、JVM自帶的類加載器有三種,如下:

  • 根(Bootstrap)類加載器:該加載器沒有父加載器,它負責加載虛擬機的核心類庫,如java.lang.*等。它從系統屬性sun.boot.class.path所指定的目錄中加載類庫,它的實現依賴於底層操作系統,屬於虛擬機的實現的一部分,它並沒有繼承java.lang.ClassLoader類,因爲它是用C++寫的,無法在代碼中訪問該類。
  • 擴展(Extension)類加載器:它的父加載器爲根類加載器,它從java.ext.dirs系統屬性所指定的目錄中加載類庫,或者從JDK的安裝目錄的jre\lib\ext子目錄(擴展目錄)下加載類庫,如果把用戶創建的jar文件放在該目錄下,也會自動由擴展類加載器加載。它是純Java類,是java.lang.ClassLoader類的子類。
  • 系統(System)類加載器:也稱爲應用類加載器,它的父加載器爲擴展類加載器。它從環境變量classpath或系統屬性java.class.path所指定的目錄中加載類,它是用戶自定義的類加載器的默認父加載器。它是純Java類,也是java.lang.ClassLoader類的子類。

如下代碼所示,由於類的加載是將類的class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。而這個Class對象會包含一個負責加載該類的類加載器的引用,所以通過Class對象的getClassLoader方法就可以得到加載該類的類加載器。以下代碼的運行結果是首先輸出“null”,然後輸出“sun.misc.Launcher$AppClassLoader@18fe7c3”,因爲根類加載器它負責加載虛擬機的核心類庫,所以代碼“Class<?> clazz = Class.forName("java.lang.String")”會由根類加載器進行加載,而根類加載器是無法在代碼中訪問到的,所以“clazz.getClassLoader()”返回null代表根類加載器。

package com.jgao.classloader.test05;

class User {
	
}

public class Test {

	public static void main(String[] args) throws ClassNotFoundException {
		Class<?> clazz = Class.forName("java.lang.String");
		System.out.println(clazz.getClassLoader());
		
		clazz = Class.forName("com.jgao.classloader.test05.User");
		System.out.println(clazz.getClassLoader());
	}

}

3、用戶自定義類加載器

除了以上JVM自帶的加載器外,用戶還可以定製自己的類加載器,所有用戶自定義的類加載器需要繼承java.lang.ClassLoader類。

二、父親委託機制

1、父子加載器

類加載器用來把類加載到JVM中。從JDK 1.2開始,類的加載過程採用父親委託機制,該機制能更好地保證Java平臺的安全性。在委託機制中,各個加載器按照父子關係形成了樹形結構,除了JVM自帶的根類加載器以外,其餘的類加載器都有且只有一個父類加載器,如下圖:

需要注意的是加載器之間的父子關係實際上指的是加載器對象之間的包裝關係,而不是類之間的繼承關係,所以一對父子加載器可能是同一個加載器類的兩個實例(在子加載器對象中包裝了一個父加載器對象),也可能不是。如下圖所示,這是java.lang.ClassLoader類的兩個構造方法;當生成一個自定義的類加載器實例時,如果使用的是ClassLoader類的空參數的構造方法時(也就是沒有指定它的父加載器),那麼會使用 getSystemClassLoader() 方法返回的系統類加載器作爲該類加載器的父加載器,所以在介紹系統類加載器時說它是用戶自定義的類加載器的默認父加載器。而使用另外一個構造方法就可以指定父加載器。

2、父親委託機制加載流程

如下圖所示,當Java程序請求加載器loader2加載User類時,加載器load2首先從自己的命名空間中查找User類是否已經被加載了,如果已經加載,則直接返回代表User類的Class對象的引用。否則,loader2首先請求父加載器loader1代爲加載,loader1再繼續請求它的父加載器(系統類加載器)代爲加載,系統類加載器再請求擴展類加載器代爲加載,擴展類加載器再請求根類加載器代爲加載。若根類加載器和擴展類加載器都不能加載,則系統類加載器嘗試加載,若能加載成功,則將User所對應的Class對象的引用返回給loader1,loader1再將引用返回給loader2,從而成功將User類加載進虛擬機。若系統類加載器不能加載User類,則loader1嘗試加載User類,若loader1也不能成功加載,則loader2嘗試加載。若所有的父加載器及loader2本身都不能加載,則拋出ClassNotFoundException異常。

在這整個過程中,如果有一個類加載器能夠成功加載User類,那麼這個類加載器被稱爲定義類加載器,所有能成功返回Class對象的引用的類加載器(包括定義類加載器)都被稱爲初始類加載器。比如loader1實際加載了User類,則loader1爲User類的定義類加載器,loader2和loader1爲User類的初始類加載器。

3、父親委託機制的優點

父親委託機制的優點就是能夠提高軟件系統的安全性,因爲在此機制下,用戶自定義的類加載器不可能加載應該由父加載器加載的可靠類,從而防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼。比如java.lang.Object類總是由根類加載器加載,其他任何用戶自定義的類加載器都不可能加載含有惡意代碼的java.lang.Object類。

三、其他

1、命名空間

每個類加載器都有自己的命名空間,命名空間由該類加載器及所有父加載器所加載的類組成。在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類;在不同的命名空間中,有可能會出現類的完整名字相同的兩個類。比如有兩個類加載器A和B,A類加載器已經加載了C這個類,那麼B這個類加載器還能加載C這個類嗎?這要分兩種情況,如下:

  • A和B加載器不是父子關係:這時B這個類加載器還是可以加載C這個類
  • A和B加載器是父子關係:這時B這個類加載器就不可以加載C這個類,因爲已經由A加載器加載過了,就不會再次進行加載

2、運行時包

由同一類加載器加載的屬於相同包的類組成了運行時包。決定兩個類是不是屬於同一個運行時包,不僅要看它們的包名是否相同,還要看定義類加載器是否相同,只有屬於同一運行時包的類才能互相訪問包可見(即默認的訪問級別)的類和類成員。這樣的限制能避免用戶自定義的類冒充核心類庫的類,去訪問核心類庫的包可見成員。比如用戶自定義了一個類java.lang.Spy,並由用戶自定義的類加載器加載,由於java.lang.Spy和核心類庫java.lang.*是由不同的類加載器加載,它們屬於不同的運行時包,所以java.lang.Spy不能訪問核心類庫java.lang包中的包可見成員。

發佈了60 篇原創文章 · 獲贊 10 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章