JAVA雙親委派機(jī)制詳解及實際應(yīng)用場景
什么是雙親委派機(jī)制
雙親委派機(jī)制(Parents Delegation Model)是JVM中類加載器的一種工作機(jī)制。當(dāng)一個類加載器收到類加載請求時,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成。只有當(dāng)父類加載器無法完成加載請求時,子類加載器才會嘗試自己去加載。
這種機(jī)制確保了Java核心API的類不會被隨意替換,維護(hù)了Java運(yùn)行環(huán)境的安全性和穩(wěn)定性。
類加載器的層級結(jié)構(gòu)
Java中的類加載器按照層級關(guān)系分為以下幾種:
1. 啟動類加載器(Bootstrap ClassLoader)
- 位置:JVM內(nèi)部實現(xiàn),由C++代碼實現(xiàn)
- 作用:加載Java核心類庫(如
java.lang.*、java.util.*等) - 路徑:
$JAVA_HOME/lib目錄下的類庫 - 特點:最頂層的類加載器,沒有父類加載器
2. 擴(kuò)展類加載器(Extension ClassLoader)
- 位置:
sun.misc.Launcher$ExtClassLoader - 作用:加載擴(kuò)展類庫
- 路徑:
$JAVA_HOME/lib/ext目錄下的類庫 - 父類加載器:啟動類加載器
3. 應(yīng)用程序類加載器(Application ClassLoader)
- 位置:
sun.misc.Launcher$AppClassLoader - 作用:加載應(yīng)用程序類路徑(ClassPath)上的類
- 路徑:環(huán)境變量ClassPath指定的路徑
- 父類加載器:擴(kuò)展類加載器
- 特點:也稱為系統(tǒng)類加載器
4. 自定義類加載器(Custom ClassLoader)
- 作用:用戶根據(jù)需要自定義的類加載器
- 父類加載器:通常是應(yīng)用程序類加載器
// 查看類加載器層級結(jié)構(gòu)的示例代碼
public class ClassLoaderHierarchy {
public static void main(String[] args) {
// 獲取當(dāng)前類的類加載器
ClassLoader classLoader = ClassLoaderHierarchy.class.getClassLoader();
System.out.println("當(dāng)前類的類加載器:" + classLoader);
// 獲取父類加載器
ClassLoader parentClassLoader = classLoader.getParent();
System.out.println("父類加載器:" + parentClassLoader);
// 獲取祖父類加載器
ClassLoader grandParentClassLoader = parentClassLoader.getParent();
System.out.println("祖父類加載器:" + grandParentClassLoader);
// 輸出結(jié)果:
// 當(dāng)前類的類加載器:sun.misc.Launcher$AppClassLoader@2a139a55
// 父類加載器:sun.misc.Launcher$ExtClassLoader@15db9742
// 祖父類加載器:null (Bootstrap ClassLoader由C++實現(xiàn),在Java中顯示為null)
}
}
雙親委派機(jī)制的工作原理
雙親委派機(jī)制的工作流程如下:
- 接收加載請求:類加載器接收到類加載請求
- 向上委派:不立即加載,而是委派給父類加載器
- 遞歸委派:父類加載器繼續(xù)向上委派,直到啟動類加載器
- 嘗試加載:啟動類加載器嘗試加載類
- 向下返回:如果加載失敗,返回給子類加載器嘗試加載
- 最終加載:直到某個類加載器成功加載類或全部失敗
graph TD
A[自定義類加載器] --> B[應(yīng)用程序類加載器]
B --> C[擴(kuò)展類加載器]
C --> D[啟動類加載器]
D --> E{能否加載?}
E -->|能| F[加載完成]
E -->|不能| G[委派給子類加載器]
G --> H{擴(kuò)展類加載器能否加載?}
H -->|能| I[加載完成]
H -->|不能| J[委派給子類加載器]
J --> K{應(yīng)用程序類加載器能否加載?}
K -->|能| L[加載完成]
K -->|不能| M[委派給子類加載器]
M --> N{自定義類加載器能否加載?}
N -->|能| O[加載完成]
N -->|不能| P[拋出ClassNotFoundException]
為什么需要雙親委派機(jī)制
1. 避免類的重復(fù)加載
如果沒有雙親委派機(jī)制,每個類加載器都可能加載同一個類,導(dǎo)致內(nèi)存中存在多個相同的類對象。
2. 保證Java核心API的安全性
防止核心API被惡意替換。例如,如果有人自定義了一個java.lang.String類,通過雙親委派機(jī)制,最終會由啟動類加載器加載JDK中的String類,而不是用戶自定義的類。
3. 保證類的唯一性
在JVM中,類的唯一性是由類加載器和類的全限定名共同決定的。雙親委派機(jī)制確保了同一個類只會被同一個類加載器加載一次。
雙親委派機(jī)制的源碼分析
讓我們來看看ClassLoader類中loadClass方法的實現(xiàn):
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先檢查該類是否已經(jīng)被加載
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果有父類加載器,委派給父類加載器加載
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 如果沒有父類加載器,說明是啟動類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器拋出ClassNotFoundException
// 說明父類加載器無法完成加載請求
}
if (c == null) {
// 如果父類加載器無法加載,則調(diào)用自己的findClass方法進(jìn)行加載
long t1 = System.nanoTime();
c = findClass(name);
// 記錄加載時間統(tǒng)計
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
雙親委派機(jī)制的破壞
雖然雙親委派機(jī)制很重要,但在某些場景下需要被破壞:
1. 自定義類加載器
通過重寫loadClass方法來改變類加載的行為:
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先檢查是否已經(jīng)加載
Class<?> c = findLoadedClass(name);
if (c == null) {
// 對于自定義的類,直接由當(dāng)前類加載器加載
if (name.startsWith("com.example.")) {
c = findClass(name);
} else {
// 其他類仍然遵循雙親委派
c = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 實現(xiàn)自定義的類加載邏輯
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String name) {
// 從自定義位置加載類的字節(jié)碼
// 這里可以從網(wǎng)絡(luò)、數(shù)據(jù)庫等位置加載
return null; // 簡化示例
}
}
2. 線程上下文類加載器
在某些情況下,父類加載器需要加載由子類加載器加載的類,這時可以使用線程上下文類加載器:
public class ContextClassLoaderExample {
public static void main(String[] args) {
// 獲取當(dāng)前線程的上下文類加載器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println("上下文類加載器:" + contextClassLoader);
// 設(shè)置自定義的上下文類加載器
Thread.currentThread().setContextClassLoader(new CustomClassLoader());
// 在某些框架中,會使用上下文類加載器來加載類
// 例如:JDBC驅(qū)動加載、Spring容器等
}
}
3. 熱替換和熱部署
在開發(fā)環(huán)境中,為了實現(xiàn)熱替換功能,需要破壞雙親委派機(jī)制:
public class HotSwapClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class<?> c = findLoadedClass(name);
if (c == null) {
// 對于需要熱替換的類,每次都重新加載
if (isHotSwapClass(name)) {
c = findClass(name);
} else {
c = super.loadClass(name, resolve);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
private boolean isHotSwapClass(String name) {
// 判斷是否是需要熱替換的類
return name.startsWith("com.example.hotswap");
}
}
實際應(yīng)用場景
1. Web應(yīng)用服務(wù)器
Tomcat等Web服務(wù)器為了實現(xiàn)應(yīng)用隔離,每個Web應(yīng)用都有自己的類加載器:
// Tomcat的類加載器層級結(jié)構(gòu) // Bootstrap ClassLoader // | // System ClassLoader // | // Common ClassLoader // | // Catalina ClassLoader Shared ClassLoader // | // WebApp ClassLoader
2. OSGi框架
OSGi框架完全破壞了雙親委派機(jī)制,實現(xiàn)了網(wǎng)狀的類加載器結(jié)構(gòu)。
3. 模塊化系統(tǒng)
Java 9的模塊系統(tǒng)也對雙親委派機(jī)制進(jìn)行了一定的改進(jìn)。
總結(jié)
雙親委派機(jī)制是Java類加載器的核心機(jī)制,它具有以下特點:
優(yōu)點:
- 避免類的重復(fù)加載
- 保證Java核心API的安全性
- 維護(hù)類的唯一性
缺點:
- 在某些場景下過于嚴(yán)格,需要被破壞
- 可能導(dǎo)致類加載的性能問題
適用場景:
- 大部分標(biāo)準(zhǔn)Java應(yīng)用
- 需要保證類加載安全性的場景
破壞場景:
- 自定義類加載器
- 熱替換和熱部署
- 模塊化系統(tǒng)
- Web應(yīng)用服務(wù)器
到此這篇關(guān)于JAVA雙親委派機(jī)制的文章就介紹到這了,更多相關(guān)JAVA雙親委派機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot前端傳遞數(shù)組后端接收兩種常用的方法
這篇文章主要給大家介紹了關(guān)于SpringBoot前端傳遞數(shù)組后端接收兩種常用的方法,文中通過代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考借鑒價值,需要的朋友可以參考下2024-04-04
PostgreSQL Docker部署+SpringBoot集成方式
本文介紹了如何在Docker中部署PostgreSQL和pgadmin,并通過SpringBoot集成PostgreSQL,主要步驟包括安裝PostgreSQL和pgadmin,配置防火墻,創(chuàng)建數(shù)據(jù)庫和表,以及在SpringBoot中配置數(shù)據(jù)源和實體類2024-12-12
Java中Date數(shù)據(jù)類型的數(shù)值轉(zhuǎn)換方式
這篇文章主要介紹了Java中Date數(shù)據(jù)類型的數(shù)值轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07

