JVM類加載機制與雙親委派使用及說明
一、JVM 四大類加載器
JVM 的類加載器(ClassLoader)負責(zé)將.class字節(jié)碼文件加載到內(nèi)存中,并生成對應(yīng)的Class對象。
Java 默認提供了四層層級結(jié)構(gòu)的類加載器,從上到下依次為:啟動類加載器、擴展類加載器、應(yīng)用程序類加載器、自定義類加載器。
1. 層級結(jié)構(gòu)與核心特性
| 類加載器名稱 | 英文 / 簡稱 | 加載范圍 | 實現(xiàn)方式 | 父加載器 |
|---|---|---|---|---|
| 啟動類加載器 | Bootstrap ClassLoader | JRE 核心類庫:rt.jar、resources.jar、sun.boot.class.path路徑下的類(如java.lang.*、java.util.*) | C/C++ 實現(xiàn),JVM 內(nèi)置,無對應(yīng) Java 對象,無法在代碼中直接獲取 | 無(頂層加載器) |
擴展類加載器 (jdk9+后改名為平臺類加載器) | Extension ClassLoader | JRE 擴展目錄jre/lib/ext、java.ext.dirs指定路徑下的擴展類 | Java 實現(xiàn),繼承自URLClassLoader | 啟動類加載器 |
| 應(yīng)用程序類加載器 | Application ClassLoader(系統(tǒng)類加載器) | 項目classpath下的自定義類、第三方依賴包 | Java 實現(xiàn),繼承自URLClassLoader,是ClassLoader.getSystemClassLoader()的返回值 | 擴展類加載器 |
| 自定義類加載器 | Custom ClassLoader | 自定義路徑的類(網(wǎng)絡(luò)、加密字節(jié)碼、自定義目錄等) | 繼承ClassLoader重寫核心方法實現(xiàn) | 默認為應(yīng)用程序類加載器 |
補充關(guān)鍵說明
- 層級關(guān)系≠繼承關(guān)系:類加載器的父子關(guān)系是組合引用(通過
parent字段關(guān)聯(lián)),不是 Java 類的繼承; - 獲取系統(tǒng)類加載器:代碼中調(diào)用
ClassLoader.getSystemClassLoader(),默認獲取的就是應(yīng)用程序類加載器; - Bootstrap 特殊點:它是 JVM 本地代碼實現(xiàn)的,在 Java 中獲取它會返回
null。
2. 代碼驗證類加載器
通過簡單代碼查看不同類的加載器,直觀理解層級關(guān)系:
public class ClassLoaderDemo {
public static void main(String[] args) {
// 1. 核心類:由Bootstrap加載,輸出null
ClassLoader bootstrapLoader = String.class.getClassLoader();
System.out.println("String類加載器: " + bootstrapLoader);
// 2. 系統(tǒng)類加載器(應(yīng)用類加載器)
ClassLoader appLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println("自定義類加載器: " + appLoader);
// 3. 應(yīng)用類加載器的父加載器:擴展類加載器
ClassLoader extLoader = appLoader.getParent();
System.out.println("應(yīng)用類加載器的父加載器: " + extLoader);
// 4. 擴展類加載器的父加載器:Bootstrap,輸出null
System.out.println("擴展類加載器的父加載器: " + extLoader.getParent());
}
}
二、雙親委派機制
1. 定義
雙親委派機制是 JVM 類加載的默認規(guī)則:當一個類加載器收到類加載請求時,不會自己先嘗試加載,而是將請求向上委托給父加載器,遞歸向上直到頂層啟動類加載器;只有當父加載器無法加載該類時,子加載器才會嘗試自己加載。
2. 執(zhí)行流程(遞歸邏輯)
自定義類加載器收到加載請求,先檢查是否已加載該類,已加載則直接返回Class對象;
未加載則委托給父加載器(應(yīng)用類加載器);
應(yīng)用類加載器重復(fù)檢查邏輯,委托給擴展類加載器;
擴展類加載器重復(fù)檢查邏輯,委托給啟動類加載器;
啟動類加載器嘗試在自身加載路徑查找類:
- 找到:直接加載并返回
Class對象; - 未找到:向下回溯,讓子加載器嘗試加載;
若所有層級加載器都無法加載,拋出ClassNotFoundException。
3. 核心優(yōu)勢
避免類重復(fù)加載:保證同一個類在 JVM 中只被加載一次,生成唯一的Class對象;
保障 Java 核心 API 安全(沙箱安全):防止惡意代碼自定義核心類(如java.lang.String)替換 JDK 原生類,避免核心 API 被篡改;
層級管理類資源:規(guī)范不同范圍類的加載邊界,提升類加載的穩(wěn)定性。
4. 源碼層面解析
雙親委派的核心邏輯封裝在ClassLoader的loadClass(String name, boolean resolve)方法中(JDK8 源碼核心邏輯):
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 檢查類是否已經(jīng)被當前加載器加載
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 遞歸委托父加載器加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 3. 無父加載器,直接使用啟動類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加載器無法加載,捕獲異常
}
// 4. 父加載器加載失敗,當前加載器自行加載
if (c == null) {
c = findClass(name);
}
}
// 解析類
if (resolve) {
resolveClass(c);
}
return c;
}
}
關(guān)鍵方法:findClass()是子類的擴展點,默認拋出異常,自定義加載器需要重寫它。
三、打破雙親委派機制
1. 為什么要打破?
默認的雙親委派無法滿足所有場景,常見需求:
- 加載自定義路徑 / 特殊來源的類(網(wǎng)絡(luò)字節(jié)碼、加密字節(jié)碼、模塊化 jar);
- 實現(xiàn)類隔離(如 Web 容器 Tomcat、SPI 機制、熱部署);
- 同一個應(yīng)用中需要加載同一個類的不同版本。
2. 打破的核心原理
雙親委派的邏輯寫在loadClass()方法中,重寫loadClass()方法,跳過向上委托的邏輯,就能直接打破該機制。
3. 標準實現(xiàn)方式:自定義類加載器
步驟
- 繼承
java.lang.ClassLoader抽象類; - 重寫
loadClass()方法,破壞默認的雙親委派遞歸邏輯; - 重寫
findClass()方法,實現(xiàn)自定義的類字節(jié)碼讀取邏輯。
完整示例代碼
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
/**
* 打破雙親委派的自定義類加載器
*/
public class CustomClassLoader extends ClassLoader {
// 自定義類加載路徑
private final String classPath;
public CustomClassLoader(String classPath) {
// 關(guān)鍵:不指定父加載器(默認父加載器為系統(tǒng)類加載器,也可手動設(shè)置)
this.classPath = classPath;
}
/**
* 重寫loadClass,核心:跳過雙親委派的向上委托邏輯
*/
@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 檢查是否已加載
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
// 2. 打破規(guī)則:核心類仍交給Bootstrap加載,避免JDK核心類加載異常
if (name.startsWith("java.")) {
return getSystemClassLoader().loadClass(name);
}
// 3. 不委托父加載器,直接自己加載
try {
clazz = findClass(name);
} catch (Exception e) {
// 加載失敗,降級為系統(tǒng)類加載器加載
return super.loadClass(name, resolve);
}
if (resolve) {
resolveClass(clazz);
}
return clazz;
}
}
/**
* 重寫findClass:從自定義路徑讀取字節(jié)碼
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassBytes(name);
if (classData == null) {
throw new ClassNotFoundException("無法找到類:" + name);
}
// 將字節(jié)碼轉(zhuǎn)換為Class對象
return defineClass(name, classData, 0, classData.length);
}
/**
* 從文件系統(tǒng)讀取.class字節(jié)碼
*/
private byte[] getClassBytes(String className) {
String path = classPath + "/" + className.replace(".", "/") + ".class";
try (FileInputStream fis = new FileInputStream(path);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
return null;
}
}
}
測試代碼
public class TestCustomLoader {
public static void main(String[] args) throws Exception {
// 自定義加載路徑
CustomClassLoader loader = new CustomClassLoader("D:/test/classes");
// 加載自定義類
Class<?> clazz = loader.loadClass("com.test.DemoClass");
// 打印加載器,驗證為自定義加載器
System.out.println("類加載器: " + clazz.getClassLoader());
}
}
4. 行業(yè)中打破雙親委派的經(jīng)典場景
場景 1:Tomcat 等 Web 容器
- 需求:一個 Web 服務(wù)器部署多個應(yīng)用,不同應(yīng)用可能依賴同一個類的不同版本,需要類隔離;
- 實現(xiàn):每個 Web 應(yīng)用對應(yīng)一個獨立的
WebAppClassLoader,優(yōu)先加載應(yīng)用自身的類,跳過雙親委派向上委托邏輯,避免類沖突。
場景 2:JDBC SPI 服務(wù)發(fā)現(xiàn)
- JDK 的
rt.jar中定義了java.sql.Driver接口(由 Bootstrap 加載),但具體驅(qū)動實現(xiàn)類(MySQL/PostgreSQL 驅(qū)動)在classpath下; - Bootstrap 加載器無法加載應(yīng)用層的驅(qū)動類,因此通過
Thread.getContextClassLoader()(線程上下文類加載器)打破委派,讓啟動類加載器反向使用應(yīng)用類加載器加載實現(xiàn)類。
場景 3:熱部署 / 熱加載
- 如 JRebel、Spring Boot DevTools,通過自定義類加載器重新加載修改后的類,繞過默認的緩存機制,實現(xiàn)代碼熱更新。
總結(jié)
核心知識點回顧:
- 四大類加載器:Bootstrap(頂層、C 實現(xiàn))→ Extension → Application → 自定義加載器,層級為組合關(guān)系而非繼承;
- 雙親委派機制:向上委托、向下加載,核心價值是防重復(fù)加載、保障核心 API 安全,邏輯在
ClassLoader.loadClass()中; - 打破方式:重寫
loadClass()方法跳過向上委托,配合自定義findClass()實現(xiàn)個性化加載; - 工業(yè)級場景:Tomcat 類隔離、JDBC SPI、熱部署框架是打破雙親委派的典型應(yīng)用。
面試答題話術(shù)參考:
- JVM 有四層類加載器,從頂層到底層分別是啟動類、擴展類、應(yīng)用類和自定義類加載器。
- 雙親委派是類加載的默認規(guī)則,請求會遞歸向上委托給父加載器,父加載器無法加載時子加載器才會處理,主要作用是避免類重復(fù)和保證核心類安全。
- 打破該機制的核心方法是自定義 ClassLoader 并重寫 loadClass 方法,跳過委托邏輯,像 Tomcat 的類隔離、JDBC 的 SPI 機制都是實際業(yè)務(wù)中打破該機制的經(jīng)典場景。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實現(xiàn)html轉(zhuǎn)pdf方法步驟
這篇文章主要給大家介紹了關(guān)于java實現(xiàn)html轉(zhuǎn)pdf方法的相關(guān)資料,要將HTML轉(zhuǎn)換成PDF,我們需要借助Java中的第三方庫,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2023-08-08
java線程中synchronized和Lock區(qū)別及介紹
這篇文章主要為大家介紹了java線程中synchronized和Lock區(qū)別及介紹,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06
SpringBoot CountDownLatch多任務(wù)并行處理的實現(xiàn)方法
本篇文章主要介紹了SpringBoot CountDownLatch多任務(wù)并行處理的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04
springboot2.0使用Hikari連接池的方法(替換druid)
這篇文章主要介紹了springboot 2.0使用Hikari連接池的方法(替換druid),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
Java使用Callable接口實現(xiàn)多線程的實例代碼
這篇文章主要介紹了Java使用Callable接口實現(xiàn)多線程的實例代碼,實現(xiàn)Callable和實現(xiàn)Runnable類似,但是功能更強大,具體表現(xiàn)在可以在任務(wù)結(jié)束后提供一個返回值,Runnable不行,call方法可以拋出異,Runnable的run方法不行,需要的朋友可以參考下2023-08-08
idea導(dǎo)入springboot項目沒有maven的解決
這篇文章主要介紹了idea導(dǎo)入springboot項目沒有maven的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
springboot集成JWT之雙重token的實現(xiàn)
本文主要介紹了springboot集成JWT之雙重token的實現(xiàn),前端使用accessToken進行登錄和驗證,后端使用refreshToken定期更新accessToken,具有一定的參考價值,感興趣的可以了解一下2025-03-03

