JVM 類加載器之間存在一種層次關系,通常被稱為雙親委派模型 (Parent Delegation Model)。這種層次關系和委托機制是 Java 類加載機制的核心,對于保證 Java 程序的安全性和避免類沖突至關重要。
1. 類加載器的層次關系:
JVM 中的類加載器(ClassLoader)主要分為以下幾種,它們之間存在自頂向下的層次關系(父子關系,但不是繼承關系,而是組合關系):
-
啟動類加載器 (Bootstrap Class Loader):
- 最頂層的類加載器。
- 負責加載 Java 核心類庫,例如
java.lang
、java.util
、java.io
等包中的類(通常位于<JAVA_HOME>/jre/lib
目錄下的rt.jar
、resources.jar
等)。 - 用 C/C++ 實現,是 JVM 的一部分,不是 Java 類。
- 沒有父類加載器。
- 無法通過 Java 代碼直接獲取到啟動類加載器。
-
擴展類加載器 (Extension Class Loader):
- 父類加載器是啟動類加載器。
- 負責加載 Java 擴展類庫(通常位于
<JAVA_HOME>/jre/lib/ext
目錄下的 jar 包,或者由java.ext.dirs
系統屬性指定的目錄)。 - 是
sun.misc.Launcher$ExtClassLoader
的實例(Java 類)。
-
應用程序類加載器 (Application Class Loader / System Class Loader):
- 父類加載器是擴展類加載器。
- 負責加載應用程序的類(classpath 下的類,包括你寫的代碼、第三方庫等)。
- 是
sun.misc.Launcher$AppClassLoader
的實例(Java 類)。 - 通常是默認的類加載器。 可以通過
ClassLoader.getSystemClassLoader()
獲取。
-
自定義類加載器 (User-Defined Class Loader):
- 父類加載器通常是應用程序類加載器(也可以是其他自定義類加載器)。
- 由開發者自定義,繼承
java.lang.ClassLoader
類。 - 用于實現特殊的類加載邏輯,例如:
- 從網絡加載類。
- 從數據庫加載類。
- 對類進行加密和解密。
- 實現熱部署(HotSwap)。
- 實現模塊化(OSGi)。
類加載器層次關系圖示:
+-----------------------------+| Bootstrap Class Loader | (C/C++)+-----------------------------+↑| (Parent)|+-----------------------------+| Extension Class Loader | (sun.misc.Launcher$ExtClassLoader)+-----------------------------+↑| (Parent)|+-----------------------------+| Application Class Loader | (sun.misc.Launcher$AppClassLoader)+-----------------------------+↑| (Parent)|+-----------------------------+| User-Defined Class Loader | (extends java.lang.ClassLoader)+-----------------------------+
2. 雙親委派模型 (Parent Delegation Model):
-
工作原理:
- 當一個類加載器需要加載類時,它首先不會自己嘗試加載,而是 委托給它的父類加載器 去加載。
- 父類加載器會檢查自己是否已經加載過該類。如果已經加載,則直接返回
Class
對象。 - 如果父類加載器沒有加載過該類,則會嘗試加載。如果父類加載器在其搜索范圍內找到了該類,則加載成功,并返回
Class
對象。 - 如果父類加載器無法加載該類(在其搜索范圍內找不到該類),則 將加載請求返回給子類加載器。
- 子類加載器嘗試加載該類。如果子類加載器能夠加載,則加載成功,并返回
Class
對象。 - 如果子類加載器也無法加載該類,則拋出
ClassNotFoundException
異常。
-
特例: 啟動類加載器沒有父類加載器,它會直接嘗試加載。
-
代碼示例 (簡化版):
// java.lang.ClassLoader 中的 loadClass 方法 (簡化版) protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// 1. 檢查該類是否已經被加載過Class<?> c = findLoadedClass(name);if (c == null) {try {// 2. 如果有父類加載器,則委托給父類加載器加載if (parent != null) {c = parent.loadClass(name, false);} else {// 3. 如果沒有父類加載器 (到達了啟動類加載器),則調用 findBootstrapClassOrNullc = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 父類加載器無法加載該類}if (c == null) {// 4. 如果父類加載器無法加載,則調用 findClass 方法嘗試自己加載c = findClass(name);}}if (resolve) {resolveClass(c); // 鏈接類 (可選)}return c;} }
findClass()
方法是需要子類加載器重寫的方法, 用于實現自定義的類加載邏輯.
3. 雙親委派模型的優點:
- 避免類的重復加載: 同一個類只會被加載一次,避免了命名沖突和資源浪費。
- 保證 Java 核心類庫的安全性: 用戶自定義的類加載器無法加載或替換 Java 核心類庫中的類(例如
java.lang.Object
、java.lang.String
),因為核心類庫總是由啟動類加載器加載。這可以防止惡意代碼篡改核心類庫,破壞 JVM 的安全性。 - 命名空間隔離: 不同的類加載器加載的類位于不同的命名空間, 可以防止類名沖突.
4. 破壞雙親委派模型:
雖然雙親委派模型有很多優點,但在某些情況下,可能需要破壞雙親委派模型,例如:
-
JDBC、JNDI、JAXP 等 SPI (Service Provider Interface) 機制:
- 這些 API 的核心接口是由啟動類加載器加載的,但具體的實現類通常由應用程序類加載器或自定義類加載器加載。
- 為了解決這個問題,Java 引入了線程上下文類加載器 (Thread Context ClassLoader)。
- 可以通過
Thread.currentThread().getContextClassLoader()
獲取線程上下文類加載器。 - 例如,JDBC 驅動程序通常由應用程序類加載器加載,但 JDBC API (如
java.sql.DriverManager
) 需要能夠加載這些驅動程序。DriverManager
會使用線程上下文類加載器來加載驅動程序。
-
OSGi (Open Service Gateway initiative):
- OSGi 是一個模塊化系統,每個模塊(bundle)都有自己的類加載器。
- OSGi 的類加載器模型不是嚴格的雙親委派模型,而是更復雜的網狀結構。
-
熱部署 (HotSwap):
- 在應用程序運行時,動態地替換或更新類。
- 通常需要自定義類加載器,并破壞雙親委派模型。
-
Tomcat:
- Tomcat 為了實現 web 應用之間的隔離,以及共享庫的加載,破壞了雙親委派模型。
- 每個 web 應用都有自己的類加載器 (WebAppClassLoader)。
總結:
JVM 類加載器之間存在層次關系(啟動類加載器、擴展類加載器、應用程序類加載器、自定義類加載器),并通過雙親委派模型協同工作。雙親委派模型保證了類加載的順序和安全性,避免了類的重復加載,并防止了核心類庫被篡改。 在某些特殊情況下,可能需要破壞雙親委派模型(例如,SPI、OSGi、熱部署)。