Java類(lèi)加載策略之雙親委派機(jī)制全面分析講解
前言
ava虛擬機(jī)(JVM)的類(lèi)加載機(jī)制是Java應(yīng)用中不可或缺的一部分。本文將詳細(xì)介紹JVM的雙親委派機(jī)制,并闡述各關(guān)鍵點(diǎn)。
一、什么是雙親委派機(jī)制
雙親委派機(jī)制(Parent-Delegate Model)是Java類(lèi)加載器中采用的一種類(lèi)加載策略。該機(jī)制的核心思想是:如果一個(gè)類(lèi)加載器收到了類(lèi)加載請(qǐng)求,默認(rèn)先將該請(qǐng)求委托給其父類(lèi)加載器處理。只有當(dāng)父級(jí)加載器無(wú)法加載該類(lèi)時(shí),才會(huì)嘗試自行加載。
二、類(lèi)加載器與層級(jí)關(guān)系
Java中的類(lèi)加載器主要有如下三種:
- 啟動(dòng)類(lèi)加載器(Bootstrap ClassLoader): 負(fù)責(zé)加載
%JAVA_HOME%/jre/lib
目錄下的核心Java類(lèi)庫(kù)如 rt.jar、charsets.jar 等。 - 擴(kuò)展類(lèi)加載器(Extension ClassLoader): 負(fù)責(zé)加載
%JAVA_HOME%/jre/lib/ext
目錄下的擴(kuò)展類(lèi)庫(kù)。 - 應(yīng)用類(lèi)加載器(Application ClassLoader): 負(fù)責(zé)加載用戶(hù)類(lèi)路徑(
ClassPath
)下的應(yīng)用程序類(lèi)。
這三種類(lèi)加載器之間存在父子層級(jí)關(guān)系。啟動(dòng)類(lèi)加載器是最高級(jí)別的加載器,沒(méi)有父加載器;擴(kuò)展類(lèi)加載器的父加載器是啟動(dòng)類(lèi)加載器;應(yīng)用類(lèi)加載器的父加載器是擴(kuò)展類(lèi)加載器。
除了以上三個(gè)內(nèi)置類(lèi)加載器,用戶(hù)還可以通過(guò)繼承 java.lang.ClassLoader 類(lèi)自定義類(lèi)加載器,根據(jù)實(shí)際需求處理類(lèi)加載請(qǐng)求。
三、雙親委派機(jī)制作用及如何破環(huán)機(jī)制
通過(guò)上述兩塊內(nèi)容,我們對(duì)雙親委派機(jī)制、加載流程及層級(jí)有了一些了解,這時(shí)我們不妨拋出幾個(gè)疑問(wèn)。
- 為什么需要雙親委派
- 雙親委派機(jī)制有哪些優(yōu)缺點(diǎn)
- 如何打破這個(gè)機(jī)制
- 有哪些工具選擇了破壞機(jī)制。
為什么需要雙親委派
1. 通過(guò)雙親委派機(jī)制,可以避免類(lèi)的重復(fù)加載,當(dāng)父加載器已經(jīng)加載過(guò)某一個(gè)類(lèi)時(shí),子加載器就不會(huì)再重新加載這個(gè)類(lèi)。
2. 通過(guò)雙親委派機(jī)制,可以保證安全性。因?yàn)锽ootstrapClassLoader在加載的時(shí)候,只會(huì)加載JAVA_HOME中的jar包里面的類(lèi),如java.lang.String,那么這個(gè)類(lèi)是不會(huì)被隨意替換的。
那么,就可以避免有人自定義一個(gè)有破壞功能的java.lang.String被加載。這樣可以有效的防止核心Java API被篡改。
這是在JDK1.8的java.lang.ClassLoader類(lèi)中的源碼,這個(gè)方法就是用于加載指定的類(lèi)。
實(shí)現(xiàn)雙親委派機(jī)制 的代碼也都集中在這個(gè)方法之中:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{ synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded 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 thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
通過(guò)以上代碼得出結(jié)論:
- 當(dāng)類(lèi)加載器接收到類(lèi)加載的請(qǐng)求時(shí),首先檢查該類(lèi)是否已經(jīng)被當(dāng)前類(lèi)加載器加載;
- 若該類(lèi)未被加載過(guò),當(dāng)前類(lèi)加載器會(huì)將加載請(qǐng)求委托給父類(lèi)加載器去完成;
- 若當(dāng)前類(lèi)加載器的父類(lèi)加載器為null,會(huì)委托啟動(dòng)類(lèi)加載器完成加載;
- 若父類(lèi)加載器無(wú)法完成類(lèi)的加載,當(dāng)前類(lèi)加載器才會(huì)去嘗試加載該類(lèi)。
雙親委派機(jī)制的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 避免重復(fù)加載:由于類(lèi)加載器直接從父類(lèi)加載器那里加載類(lèi),避免了類(lèi)的重復(fù)加載。
- 提高安全性:通過(guò)雙親委派模型,Java 標(biāo)準(zhǔn)庫(kù)中的核心類(lèi)庫(kù)(如 java.lang.*)由啟動(dòng)類(lèi)加載器加載,這樣能保證這些核心類(lèi)庫(kù)不會(huì)被惡意代碼篡改或替換,從而提高程序的安全性。
- 保持類(lèi)加載的一致性:這種方式確保了同一個(gè)類(lèi)的加載由同一個(gè)類(lèi)加載器完成,從而在運(yùn)行時(shí)保證了類(lèi)型的唯一性和相同性。這也有助于減輕類(lèi)加載器在處理相互關(guān)聯(lián)的類(lèi)時(shí)的復(fù)雜性。
缺點(diǎn):
- 靈活性降低:由于類(lèi)加載的過(guò)程需要不斷地委托給父類(lèi)加載器,這種機(jī)制可能導(dǎo)致實(shí)際應(yīng)用中類(lèi)加載的靈活性降低。
- 增加了類(lèi)加載時(shí)間:在類(lèi)加載的過(guò)程中,需要不斷地查詢(xún)并委托父類(lèi)加載器,這意味著類(lèi)加載所需要的時(shí)間可能會(huì)增加。在類(lèi)數(shù)量龐大或類(lèi)加載器層次比較深的情況下,這種時(shí)間延遲可能會(huì)變得更加明顯。
如何打破這個(gè)機(jī)制
既然上述文章中我們已經(jīng)知道了雙親委派的實(shí)現(xiàn)方式,那么如何打破這個(gè)機(jī)制呢。
想要破壞這種機(jī)制,那么就需要自定義一個(gè)類(lèi)加載器,繼承ClassLoader類(lèi)重寫(xiě)其中的loadClass方法,使其不進(jìn)行雙親委派即可。
寫(xiě)個(gè)示例
import java.lang.reflect.Method; import java.nio.file.Files; import java.nio.file.Paths; public class CustomClassLoader extends ClassLoader { // 自定義類(lèi)加載器必須提供一個(gè)加載類(lèi)文件的位置 private String classesPath; public CustomClassLoader(String classesPath, ClassLoader parent) { super(parent); this.classesPath = classesPath; } @Override protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //首先,檢查已加載的類(lèi) Class<?> loadedClass = findLoadedClass(name); if (loadedClass == null) { // 如果已加載類(lèi)中沒(méi)有該類(lèi), 嘗試用自定義的方法加載 try { loadedClass = findClassInPath(name); } catch (ClassNotFoundException e) { // 如果自定義加載方法找不到類(lèi),則委托給父類(lèi)加載器 loadedClass = super.loadClass(name, resolve); } } if (resolve) { resolveClass(loadedClass); } return loadedClass; } private Class<?> findClassInPath(String className) throws ClassNotFoundException { try { String filePath = className.replace('.', '/') + ".class"; byte[] classBytes = Files.readAllBytes(Paths.get(classesPath, filePath)); return defineClass(className, classBytes, 0, classBytes.length); } catch (Exception e) { throw new ClassNotFoundException("Class not found in classes path: " + className, e); } } public static void main(String[] args) throws Exception { String pathToClasses = "/path/to/your/classes"; String className = "com.example.SampleClass"; String methodName = "sampleMethod"; // 創(chuàng)建自定義類(lèi)加載器實(shí)例,將類(lèi)的加載權(quán)交給它 CustomClassLoader customClassLoader = new CustomClassLoader(pathToClasses, CustomClassLoader.class.getClassLoader()); // 使用自定義類(lèi)加載器加載類(lèi) Class<?> customClass = customClassLoader.loadClass(className); // 創(chuàng)建類(lèi)的實(shí)例并調(diào)用方法 Object obj = customClass.newInstance(); Method method = customClass.getDeclaredMethod(methodName); method.setAccessible(true); method.invoke(obj); } }
上面的示例代碼中,我們重寫(xiě)了 loadClass
方法,先嘗試通過(guò) findClassInPath
從指定的路徑加載類(lèi),如果無(wú)法加載就委托給父類(lèi)加載器。這樣,我們就實(shí)現(xiàn)了打破雙親委派機(jī)制的自定義類(lèi)加載器。
以下是代碼的詳細(xì)解析:
- 自定義類(lèi)加載器
CustomClassLoader
繼承 JavaClassLoader
類(lèi)。 - 在類(lèi)加載器的構(gòu)造方法中設(shè)置自定義類(lèi)加載器的類(lèi)路徑
classesPath
和父加載器parent
。 - 重寫(xiě)
loadClass
方法。首先檢查已加載的類(lèi),如果已加載則返回。否則嘗試用自定義的方法在classesPath
中加載類(lèi)。如果自定義加載方法找不到類(lèi),則委托給父類(lèi)加載器。 - 實(shí)現(xiàn)名為
findClassInPath
的自定義加載方法。這個(gè)方法使用類(lèi)名className
在classesPath
指定的目錄下查找對(duì)應(yīng)的 .class 文件,然后將文件內(nèi)容讀取為字節(jié)數(shù)組并調(diào)用defineClass
方法,將其轉(zhuǎn)換為 Java 類(lèi)的 Class 對(duì)象。如果類(lèi)不存在或出現(xiàn)其他錯(cuò)誤,會(huì)拋出ClassNotFoundException
異常。 - 在 main 方法中,創(chuàng)建一個(gè)
CustomClassLoader
類(lèi)的實(shí)例。將類(lèi)的加載任務(wù)交給自定義類(lèi)加載器,指定加載路徑和要加載的類(lèi)。 - 使用自定義類(lèi)加載器加載目標(biāo)類(lèi),創(chuàng)建類(lèi)的實(shí)例,并調(diào)用指定方法。
有哪些工具選擇了破壞機(jī)制
既然在上文中,我們已經(jīng)清楚怎么打破雙親機(jī)制,那么有哪些工具選擇了破壞機(jī)制呢?為什么?
- OSGi(Open Service Gateway Initiative):OSGi 是一個(gè)模塊化系統(tǒng)和服務(wù)平臺(tái),提供了一個(gè)強(qiáng)大的類(lèi)加載器模型。在 OSGi 中,每個(gè)模塊都有一個(gè)獨(dú)立的類(lèi)加載器,可以按需加載來(lái)自不同模塊的類(lèi)。這有助于解決 JAR 地獄問(wèn)題,提高模塊化和動(dòng)態(tài)更新能力。
- Tomcat Web容器:Tomcat 的 Web 應(yīng)用類(lèi)加載器可以加載 Web 應(yīng)用程序中的本地類(lèi)庫(kù),從而使得每個(gè) Web 應(yīng)用程序可以使用各自的版本的類(lèi)庫(kù)。這些 Web 應(yīng)用的類(lèi)加載器都是${tomcat-home}/lib 中類(lèi)庫(kù)的子類(lèi)加載器。
- Java Agent: Java Agent 是一種基于 Java Instrumentation API 的技術(shù),它可以在運(yùn)行時(shí)修改已加載的類(lèi)的字節(jié)碼,從而實(shí)現(xiàn)類(lèi)的熱替換、AOP(面向切面編程)等功能。這種技術(shù)在諸如熱部署、性能監(jiān)控和分布式追蹤等場(chǎng)景中有廣泛應(yīng)用。
- JDK 中的 URLClassLoader:JDK 自帶的 URLClassLoader 可以用來(lái)加載指定 URL 路徑下的類(lèi)。實(shí)際上,它實(shí)現(xiàn)了一種子類(lèi)優(yōu)先的策略,先嘗試加載自身路徑下的類(lèi),再委托給父類(lèi)加載器,從而打破了雙親委派機(jī)制。
這些工具和技術(shù)之所以要打破雙親委派機(jī)制,主要是出于以下原因:
- 實(shí)現(xiàn)模塊化和動(dòng)態(tài)更新:例如 OSGi,通過(guò)獨(dú)立的類(lèi)加載器實(shí)現(xiàn)不同模塊間解耦,并支持模塊的動(dòng)態(tài)卸載和更新。
- 解決類(lèi)庫(kù)版本沖突(JAR地獄問(wèn)題):在復(fù)雜系統(tǒng)中,不同模塊可能依賴(lài)不同版本的類(lèi)庫(kù)。為避免版本沖突,可使用獨(dú)立的類(lèi)加載器,使它們分別加載各自的類(lèi)庫(kù)版本。
- 運(yùn)行時(shí)修改類(lèi):Java Agent 可以在運(yùn)行時(shí)修改類(lèi)字節(jié)碼,從而支持熱替換、AOP 和性能監(jiān)控等功能。
- 支持 Web 應(yīng)用程序的獨(dú)立部署和更新:例如 Tomcat,可以為每個(gè) Web 應(yīng)用程序分配一個(gè)獨(dú)立的類(lèi)加載器,實(shí)現(xiàn)各自部署與更新。
需要注意的是,打破雙親委派機(jī)制可能會(huì)帶來(lái)類(lèi)加載沖突、安全性和性能等問(wèn)題,因此在實(shí)際應(yīng)用中要謹(jǐn)慎使用。
四、總結(jié)
本文介紹了JVM的雙親委派機(jī)制,包括概念、類(lèi)加載器層級(jí)關(guān)系、雙親委派流程及實(shí)例分析等方面的內(nèi)容。雙親委派機(jī)制可以確保Java應(yīng)用類(lèi)型安全,同時(shí)避免類(lèi)加載沖突。在某些特定場(chǎng)景下,我們可以通過(guò)自定義類(lèi)加載器對(duì)類(lèi)加載策略進(jìn)行調(diào)整,以滿(mǎn)足應(yīng)用特性和性能需求。
以上就是Java雙親委派機(jī)制全面分析講解的詳細(xì)內(nèi)容,更多關(guān)于Java雙親委派機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot以FTP方式上傳文件到遠(yuǎn)程服務(wù)器
這篇文章主要介紹了springboot以FTP方式上傳文件到遠(yuǎn)程服務(wù)器,需要的朋友可以參考下2019-12-12java生成jar包并且單進(jìn)程運(yùn)行的實(shí)例
下面小編就為大家分享一篇java生成jar包并且單進(jìn)程運(yùn)行的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12Java 中使用數(shù)組存儲(chǔ)和操作數(shù)據(jù)
本文將介紹Java中常用的數(shù)組操作方法,通過(guò)詳細(xì)的示例和解釋,幫助讀者全面理解和掌握這些方法,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09Java聊天室之使用Socket實(shí)現(xiàn)傳遞圖片
這篇文章主要為大家詳細(xì)介紹了Java簡(jiǎn)易聊天室之使用Socket實(shí)現(xiàn)傳遞圖片功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以了解一下2022-10-10Mybatis傳遞多個(gè)參數(shù)的三種實(shí)現(xiàn)方法
這篇文章主要介紹了Mybatis傳遞多個(gè)參數(shù)的三種實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java實(shí)現(xiàn)的文本字符串操作工具類(lèi)實(shí)例【數(shù)據(jù)替換,加密解密操作】
這篇文章主要介紹了Java實(shí)現(xiàn)的文本字符串操作工具類(lèi),可實(shí)現(xiàn)數(shù)據(jù)替換、加密解密等操作,涉及java字符串遍歷、編碼轉(zhuǎn)換、替換等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10