JAVA雙親委派機(jī)制詳解及實(shí)際應(yīng)用場(chǎng)景
什么是雙親委派機(jī)制
雙親委派機(jī)制(Parents Delegation Model)是JVM中類加載器的一種工作機(jī)制。當(dāng)一個(gè)類加載器收到類加載請(qǐng)求時(shí),它首先不會(huì)自己去嘗試加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類加載器去完成。只有當(dāng)父類加載器無法完成加載請(qǐng)求時(shí),子類加載器才會(huì)嘗試自己去加載。
這種機(jī)制確保了Java核心API的類不會(huì)被隨意替換,維護(hù)了Java運(yùn)行環(huán)境的安全性和穩(wěn)定性。
類加載器的層級(jí)結(jié)構(gòu)
Java中的類加載器按照層級(jí)關(guān)系分為以下幾種:
1. 啟動(dòng)類加載器(Bootstrap ClassLoader)
- 位置:JVM內(nèi)部實(shí)現(xiàn),由C++代碼實(shí)現(xiàn)
- 作用:加載Java核心類庫(如
java.lang.*、java.util.*等) - 路徑:
$JAVA_HOME/lib目錄下的類庫 - 特點(diǎn):最頂層的類加載器,沒有父類加載器
2. 擴(kuò)展類加載器(Extension ClassLoader)
- 位置:
sun.misc.Launcher$ExtClassLoader - 作用:加載擴(kuò)展類庫
- 路徑:
$JAVA_HOME/lib/ext目錄下的類庫 - 父類加載器:?jiǎn)?dòng)類加載器
3. 應(yīng)用程序類加載器(Application ClassLoader)
- 位置:
sun.misc.Launcher$AppClassLoader - 作用:加載應(yīng)用程序類路徑(ClassPath)上的類
- 路徑:環(huán)境變量ClassPath指定的路徑
- 父類加載器:擴(kuò)展類加載器
- 特點(diǎn):也稱為系統(tǒng)類加載器
4. 自定義類加載器(Custom ClassLoader)
- 作用:用戶根據(jù)需要自定義的類加載器
- 父類加載器:通常是應(yīng)用程序類加載器
// 查看類加載器層級(jí)結(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++實(shí)現(xiàn),在Java中顯示為null)
}
}
雙親委派機(jī)制的工作原理
雙親委派機(jī)制的工作流程如下:
- 接收加載請(qǐng)求:類加載器接收到類加載請(qǐng)求
- 向上委派:不立即加載,而是委派給父類加載器
- 遞歸委派:父類加載器繼續(xù)向上委派,直到啟動(dòng)類加載器
- 嘗試加載:?jiǎn)?dòng)類加載器嘗試加載類
- 向下返回:如果加載失敗,返回給子類加載器嘗試加載
- 最終加載:直到某個(gè)類加載器成功加載類或全部失敗
graph TD
A[自定義類加載器] --> B[應(yīng)用程序類加載器]
B --> C[擴(kuò)展類加載器]
C --> D[啟動(dòng)類加載器]
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ī)制,每個(gè)類加載器都可能加載同一個(gè)類,導(dǎo)致內(nèi)存中存在多個(gè)相同的類對(duì)象。
2. 保證Java核心API的安全性
防止核心API被惡意替換。例如,如果有人自定義了一個(gè)java.lang.String類,通過雙親委派機(jī)制,最終會(huì)由啟動(dòng)類加載器加載JDK中的String類,而不是用戶自定義的類。
3. 保證類的唯一性
在JVM中,類的唯一性是由類加載器和類的全限定名共同決定的。雙親委派機(jī)制確保了同一個(gè)類只會(huì)被同一個(gè)類加載器加載一次。
雙親委派機(jī)制的源碼分析
讓我們來看看ClassLoader類中loadClass方法的實(shí)現(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 {
// 如果沒有父類加載器,說明是啟動(dòng)類加載器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器拋出ClassNotFoundException
// 說明父類加載器無法完成加載請(qǐng)求
}
if (c == null) {
// 如果父類加載器無法加載,則調(diào)用自己的findClass方法進(jìn)行加載
long t1 = System.nanoTime();
c = findClass(name);
// 記錄加載時(shí)間統(tǒng)計(jì)
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ī)制很重要,但在某些場(chǎng)景下需要被破壞:
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) {
// 對(duì)于自定義的類,直接由當(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 {
// 實(shí)現(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; // 簡(jiǎn)化示例
}
}
2. 線程上下文類加載器
在某些情況下,父類加載器需要加載由子類加載器加載的類,這時(shí)可以使用線程上下文類加載器:
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());
// 在某些框架中,會(huì)使用上下文類加載器來加載類
// 例如:JDBC驅(qū)動(dòng)加載、Spring容器等
}
}
3. 熱替換和熱部署
在開發(fā)環(huán)境中,為了實(shí)現(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) {
// 對(duì)于需要熱替換的類,每次都重新加載
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");
}
}
實(shí)際應(yīng)用場(chǎng)景
1. Web應(yīng)用服務(wù)器
Tomcat等Web服務(wù)器為了實(shí)現(xiàn)應(yīng)用隔離,每個(gè)Web應(yīng)用都有自己的類加載器:
// Tomcat的類加載器層級(jí)結(jié)構(gòu) // Bootstrap ClassLoader // | // System ClassLoader // | // Common ClassLoader // | // Catalina ClassLoader Shared ClassLoader // | // WebApp ClassLoader
2. OSGi框架
OSGi框架完全破壞了雙親委派機(jī)制,實(shí)現(xiàn)了網(wǎng)狀的類加載器結(jié)構(gòu)。
3. 模塊化系統(tǒng)
Java 9的模塊系統(tǒng)也對(duì)雙親委派機(jī)制進(jìn)行了一定的改進(jìn)。
總結(jié)
雙親委派機(jī)制是Java類加載器的核心機(jī)制,它具有以下特點(diǎn):
優(yōu)點(diǎn):
- 避免類的重復(fù)加載
- 保證Java核心API的安全性
- 維護(hù)類的唯一性
缺點(diǎn):
- 在某些場(chǎng)景下過于嚴(yán)格,需要被破壞
- 可能導(dǎo)致類加載的性能問題
適用場(chǎng)景:
- 大部分標(biāo)準(zhǔn)Java應(yīng)用
- 需要保證類加載安全性的場(chǎng)景
破壞場(chǎng)景:
- 自定義類加載器
- 熱替換和熱部署
- 模塊化系統(tǒng)
- Web應(yīng)用服務(wù)器
到此這篇關(guān)于JAVA雙親委派機(jī)制的文章就介紹到這了,更多相關(guān)JAVA雙親委派機(jī)制內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Java中修飾符的總結(jié)(fina除外)
下面小編就為大家?guī)硪黄P(guān)于Java中修飾符的總結(jié)(fina除外)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
Java代碼性能優(yōu)化的35個(gè)方法總結(jié)
本篇文章主要介紹了Java代碼性能優(yōu)化的35個(gè)方法,具有很好的參考價(jià)值,下面跟著小編一起來看下吧2017-02-02
Java字符流和字節(jié)流對(duì)文件操作的區(qū)別
本篇文章主要介紹了Java的IO流分為字符流(Reader,Writer)和字節(jié)流(InputStream,OutputStream),字節(jié)流顧名思義字節(jié)流就是將文件的內(nèi)容讀取到字節(jié)數(shù)組,對(duì)初學(xué)者很有用,有需要的朋友可以了解一下。2016-10-10
SpringBoot項(xiàng)目使用yml文件鏈接數(shù)據(jù)庫異常問題解決方案
在使用SpringBoot時(shí),利用yml進(jìn)行數(shù)據(jù)庫連接配置需小心數(shù)據(jù)類型區(qū)分,如果用戶名或密碼是數(shù)字,必須用雙引號(hào)包裹以識(shí)別為字符串,避免連接錯(cuò)誤,特殊字符密碼也應(yīng)用引號(hào)包裹2024-10-10
Java AbstractMethodError案例分析詳解
這篇文章主要介紹了Java AbstractMethodError案例分析詳解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08

