JAVA雙親委派機制詳解及實際應用場景
什么是雙親委派機制
雙親委派機制(Parents Delegation Model)是JVM中類加載器的一種工作機制。當一個類加載器收到類加載請求時,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加載器去完成。只有當父類加載器無法完成加載請求時,子類加載器才會嘗試自己去加載。
這種機制確保了Java核心API的類不會被隨意替換,維護了Java運行環(huán)境的安全性和穩(wěn)定性。
類加載器的層級結構
Java中的類加載器按照層級關系分為以下幾種:
1. 啟動類加載器(Bootstrap ClassLoader)
- 位置:JVM內部實現,由C++代碼實現
- 作用:加載Java核心類庫(如
java.lang.*、java.util.*等) - 路徑:
$JAVA_HOME/lib目錄下的類庫 - 特點:最頂層的類加載器,沒有父類加載器
2. 擴展類加載器(Extension ClassLoader)
- 位置:
sun.misc.Launcher$ExtClassLoader - 作用:加載擴展類庫
- 路徑:
$JAVA_HOME/lib/ext目錄下的類庫 - 父類加載器:啟動類加載器
3. 應用程序類加載器(Application ClassLoader)
- 位置:
sun.misc.Launcher$AppClassLoader - 作用:加載應用程序類路徑(ClassPath)上的類
- 路徑:環(huán)境變量ClassPath指定的路徑
- 父類加載器:擴展類加載器
- 特點:也稱為系統(tǒng)類加載器
4. 自定義類加載器(Custom ClassLoader)
- 作用:用戶根據需要自定義的類加載器
- 父類加載器:通常是應用程序類加載器
// 查看類加載器層級結構的示例代碼
public class ClassLoaderHierarchy {
public static void main(String[] args) {
// 獲取當前類的類加載器
ClassLoader classLoader = ClassLoaderHierarchy.class.getClassLoader();
System.out.println("當前類的類加載器:" + classLoader);
// 獲取父類加載器
ClassLoader parentClassLoader = classLoader.getParent();
System.out.println("父類加載器:" + parentClassLoader);
// 獲取祖父類加載器
ClassLoader grandParentClassLoader = parentClassLoader.getParent();
System.out.println("祖父類加載器:" + grandParentClassLoader);
// 輸出結果:
// 當前類的類加載器:sun.misc.Launcher$AppClassLoader@2a139a55
// 父類加載器:sun.misc.Launcher$ExtClassLoader@15db9742
// 祖父類加載器:null (Bootstrap ClassLoader由C++實現,在Java中顯示為null)
}
}
雙親委派機制的工作原理
雙親委派機制的工作流程如下:
- 接收加載請求:類加載器接收到類加載請求
- 向上委派:不立即加載,而是委派給父類加載器
- 遞歸委派:父類加載器繼續(xù)向上委派,直到啟動類加載器
- 嘗試加載:啟動類加載器嘗試加載類
- 向下返回:如果加載失敗,返回給子類加載器嘗試加載
- 最終加載:直到某個類加載器成功加載類或全部失敗
graph TD
A[自定義類加載器] --> B[應用程序類加載器]
B --> C[擴展類加載器]
C --> D[啟動類加載器]
D --> E{能否加載?}
E -->|能| F[加載完成]
E -->|不能| G[委派給子類加載器]
G --> H{擴展類加載器能否加載?}
H -->|能| I[加載完成]
H -->|不能| J[委派給子類加載器]
J --> K{應用程序類加載器能否加載?}
K -->|能| L[加載完成]
K -->|不能| M[委派給子類加載器]
M --> N{自定義類加載器能否加載?}
N -->|能| O[加載完成]
N -->|不能| P[拋出ClassNotFoundException]
為什么需要雙親委派機制
1. 避免類的重復加載
如果沒有雙親委派機制,每個類加載器都可能加載同一個類,導致內存中存在多個相同的類對象。
2. 保證Java核心API的安全性
防止核心API被惡意替換。例如,如果有人自定義了一個java.lang.String類,通過雙親委派機制,最終會由啟動類加載器加載JDK中的String類,而不是用戶自定義的類。
3. 保證類的唯一性
在JVM中,類的唯一性是由類加載器和類的全限定名共同決定的。雙親委派機制確保了同一個類只會被同一個類加載器加載一次。
雙親委派機制的源碼分析
讓我們來看看ClassLoader類中loadClass方法的實現:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 首先檢查該類是否已經被加載
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) {
// 如果父類加載器無法加載,則調用自己的findClass方法進行加載
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;
}
}
雙親委派機制的破壞
雖然雙親委派機制很重要,但在某些場景下需要被破壞:
1. 自定義類加載器
通過重寫loadClass方法來改變類加載的行為:
public class CustomClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 首先檢查是否已經加載
Class<?> c = findLoadedClass(name);
if (c == null) {
// 對于自定義的類,直接由當前類加載器加載
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 {
// 實現自定義的類加載邏輯
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData(String name) {
// 從自定義位置加載類的字節(jié)碼
// 這里可以從網絡、數據庫等位置加載
return null; // 簡化示例
}
}
2. 線程上下文類加載器
在某些情況下,父類加載器需要加載由子類加載器加載的類,這時可以使用線程上下文類加載器:
public class ContextClassLoaderExample {
public static void main(String[] args) {
// 獲取當前線程的上下文類加載器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
System.out.println("上下文類加載器:" + contextClassLoader);
// 設置自定義的上下文類加載器
Thread.currentThread().setContextClassLoader(new CustomClassLoader());
// 在某些框架中,會使用上下文類加載器來加載類
// 例如:JDBC驅動加載、Spring容器等
}
}
3. 熱替換和熱部署
在開發(fā)環(huán)境中,為了實現熱替換功能,需要破壞雙親委派機制:
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");
}
}
實際應用場景
1. Web應用服務器
Tomcat等Web服務器為了實現應用隔離,每個Web應用都有自己的類加載器:
// Tomcat的類加載器層級結構 // Bootstrap ClassLoader // | // System ClassLoader // | // Common ClassLoader // | // Catalina ClassLoader Shared ClassLoader // | // WebApp ClassLoader
2. OSGi框架
OSGi框架完全破壞了雙親委派機制,實現了網狀的類加載器結構。
3. 模塊化系統(tǒng)
Java 9的模塊系統(tǒng)也對雙親委派機制進行了一定的改進。
總結
雙親委派機制是Java類加載器的核心機制,它具有以下特點:
優(yōu)點:
- 避免類的重復加載
- 保證Java核心API的安全性
- 維護類的唯一性
缺點:
- 在某些場景下過于嚴格,需要被破壞
- 可能導致類加載的性能問題
適用場景:
- 大部分標準Java應用
- 需要保證類加載安全性的場景
破壞場景:
- 自定義類加載器
- 熱替換和熱部署
- 模塊化系統(tǒng)
- Web應用服務器
到此這篇關于JAVA雙親委派機制的文章就介紹到這了,更多相關JAVA雙親委派機制內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot項目使用yml文件鏈接數據庫異常問題解決方案
在使用SpringBoot時,利用yml進行數據庫連接配置需小心數據類型區(qū)分,如果用戶名或密碼是數字,必須用雙引號包裹以識別為字符串,避免連接錯誤,特殊字符密碼也應用引號包裹2024-10-10
Java AbstractMethodError案例分析詳解
這篇文章主要介紹了Java AbstractMethodError案例分析詳解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-08-08

