Java虛擬機JVM學習05 類加載器的父委托機制
類加載器
類加載器用來把類加載到Java虛擬機中。
類加載器的類型
有兩種類型的類加載器:
1.JVM自帶的加載器:
根類加載器(Bootstrap)
擴展類加載器(Extension)
系統類加載器(System)
2.用戶自定義的類加載器:
java.lang.ClassLoader的子類,用戶可以定制類的加載方式。
JVM自帶的加載器
Java虛擬機自帶了以下幾種加載器。
1.根(Bootstrap)類加載器:
該加載器沒有父加載器。
它負責加載虛擬機的核心類庫,如java.lang.*等。
根類加載器從系統屬性sun.boot.class.path所指定的目錄中加載類庫。
根類加載器的實現依賴於底層操作系統,屬於虛擬機的實現的一部分,它並沒有繼承java.lang.ClassLoader類,它是用C++寫的。
2.擴展(Extension)類加載器:
它的父加載器為根類加載器。
它從java.ext.dirs系統屬性所指定的目錄中加載類庫,或者從JDK的安裝目錄的jre\lib\ext子目錄(擴展目錄)下加載類庫,如果把用戶創建的JAR文件放在這個目錄下,也會自動由擴展類加載器加載。
擴展類加載器是純Java類,是java.lang.ClassLoader類的子類。
3.系統(System)類加載器:
也稱為應用類加載器,它的父加載器為擴展類加載器。
它從環境變量classpath或者系統屬性java.class.path所指定的目錄中加載類,它是用戶自定義的類加載器的默認父加載器。
系統類加載器是純Java類,是java.lang.ClassLoader類的子類。
注意:這里的父加載器概念並不是指類的繼承關系,子加載器不一定繼承了父加載器(其實是組合的關系)。
用戶自定義類加載器
除了以上虛擬機自帶的類加載器以外,用戶還可以定制自己的類加載器(User-defined Class Loader)。
Java提供了抽象類java.lang.ClassLoader,所有用戶自定義的類加載器都應該繼承ClassLoader類。
類加載的父委托機制
從JDK 1.2版本開始,類的加載過程采用父親委托機制,這種機制能更好地保證Java平台的安全。
在父委托機制中,除了Java虛擬機自帶的根類加載器以外,其余的類加載器都有且只有一個父加載器,各個加載器按照父子關系形成了樹形結構。
當Java程序請求加載器loader1加載Sample類時,loader1首先委托自己的父加載器去加載Sample類,若父加載器能加載,則由父加載器完成加載任務,否則才由loader1本身加載Sample類。
說明具體過程的一個例子:
loader2首先從自己的命名空間中查找Sample類是否已經被加載,如果已經加載,就直接返回代表Sample類的Class對象的引用。
如果Sample類還沒有被加載,loader2首先請求loader1代為加載,loader1再請求系統類加載器代為加載,系統類加載器再請求擴展類加載器代為加載,擴展類加載器再請求根類加載器代為加載。
若根類加載器和擴展類加載器都不能加載,則系統類加載器嘗試加載,若能加載成功,則將Sample類所對應的Class對象的引用返回給loader1,loader1再返回給loader2,從而成功將Sample類加載進虛擬機。
若系統加載器不能加載Sample類,則loader1嘗試加載Sample類,若loader1也不能成功加載,則loader2嘗試加載。
若所有的父加載器及loader2本身都不能加載,則拋出ClassNotFoundException異常。
總結下來就是:
每個加載器都優先嘗試用父類加載,若父類不能加載則自己嘗試加載;若成功則返回Class對象給子類,若失敗則告訴子類讓子類自己加載。所有都失敗則拋出異常。
定義類加載器和初始類加載器
若有一個類加載器能成功加載Sample類,那么這個類加載器被稱為定義類加載器。
所有能成功返回Class對象的引用的類加載器(包括定義類加載器,即包括定義類加載器和它下面的所有子加載器)都被稱為初始類加載器。
假設loader1實際加載了Sample類,則loader1為Sample類的定義類加載器,loader2和loader1為Sample類的初始類加載器。
父子關系
需要指出的是,加載器之間的父子關系實際上指的是加載器對象之間的包裝關系,而不是類之間的繼承關系。
一對父子加載器可能是同一個加載器類的兩個實例,也可能不是。
在子加載器對象中包裝了一個父加載器對象。
例如loader1和loader2都是MyClassLoader類的實例,並且loader2包裝了loader1,loader1是loader2的父加載器。
當生成一個自定義的類加載器實例時,如果沒有指定它的父加載器(ClassLoader構造方法無參數),那么系統類加載器就將成為該類加載器的父加載器。
父委托機制優點
父親委托機制的優點是能夠提高軟件系統的安全性。
因為在此機制下,用戶自定義的類加載器不可能加載應該由父加載器加載的可靠類,從而防止不可靠甚至惡意的代碼代替由父加載器加載的可靠代碼。
例如,java.lang.Object類總是由根類加載器加載,其他任何用戶自定義的類加載器都不可能加載含有惡意代碼的java.lang.Object類。
命名空間
每個類加載器都有自己的命名空間,命名空間由該加載器及所有父加載器所加載的類組成。
在同一個命名空間中,不會出現類的完整名字(包括類的包名)相同的兩個類。
在不同的命名空間中,有可能會出現類的完整名字(包括類的包名)相同的兩個類。
運行時包
由同一類加載器加載的屬於相同包的類組成了運行時包。
決定兩個類是不是屬於同一個運行時包,不僅要看它們的包名是否相同,還要看定義類加載器是否相同。
只有屬於同一運行時包的類才能互相訪問包可見(即默認訪問級別)的類和類成員。
這樣的限制能避免用戶自定義的類冒充核心類庫的類,去訪問核心類庫的包可見成員。
假設用戶自己定義了一個類java.lang.Spy,並由用戶自定義的類加載器加載,由於java.lang.Spy和核心類庫java.lang.*由不同的類加載器加載,它們屬於不同的運行時包,所以java.lang.Spy不能訪問核心類庫java.lang包中的包可見成員。
參考資料
聖思園張龍老師Java SE系列視頻教程。