Java中類加載器舉例詳解
類加載器簡介
Java程序被編譯器編譯之后成為字節(jié)碼文件(.class文件),當(dāng)程序需要某個類時,虛擬機(jī)便會將對應(yīng)的class文件進(jìn)行加載,創(chuàng)建出對應(yīng)的Class對象。而這個將class文件加載到虛擬機(jī)內(nèi)存的過程,便是類加載。

類加載器負(fù)責(zé)在運(yùn)行時將Java類動態(tài)加載到JVM(Java虛擬機(jī)),是JRE(Java運(yùn)行時環(huán)境)的一部分。由于類加載器的存在,JVM無需了解底層文件或文件系統(tǒng)即可運(yùn)行Java程序。并且Java類不會一次全部加載到內(nèi)存中,而是在需要時才會加載到內(nèi)存中。
類加載的過程
類的生命周期通常包括:加載、鏈接、初始化、使用和卸載。上圖中包含了類加載的三個階段:加載階段、鏈接階段和初始化階段。如果將這三個階段再拆分細(xì)化包括:加載、驗(yàn)證、準(zhǔn)備、解析和初始化。

這幾個階段的作用簡單概況一下:
- 加載:通過一個類的完全限定查找類字節(jié)碼文件,轉(zhuǎn)化為方法區(qū)運(yùn)行時的數(shù)據(jù)結(jié)構(gòu),創(chuàng)建一個代表該類的Class對象。
- 驗(yàn)證:確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機(jī)要求,不會危害虛擬機(jī)自身安全。
- 準(zhǔn)備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值。不包含被final修飾的static變量,因?yàn)樗诰幾g時已經(jīng)分配了。
- 解析:將常量池內(nèi)的符號引用轉(zhuǎn)換為直接引用的過程,比如(main()方法)替換為指向數(shù)據(jù)所存內(nèi)存的指針或句柄等(直接引用),這是所謂的靜態(tài)鏈接過程(類加載期間完成),動態(tài)鏈接是在程序運(yùn)行期間完成的將符號引用替換為直接引用。如果符號引用指向一個未被加載的類,或者未被加載類的字段或方法,那么解析將觸發(fā)這個類的加載。
- 初始化:類加載最后階段,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量。對類的靜態(tài)變量初始化為指定的值,執(zhí)行靜態(tài)代碼塊。
在上述類加載的過程中,虛擬機(jī)提供了三種類加載器:啟動(Bootstrap)類加載器、擴(kuò)展(Extension)類加載器、系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器),此外還可以自定義類加載器。


啟動(Bootstrap)類加載器:
系統(tǒng)中最頂層、最核心的加載器,是 JVM 與生俱來的一部分,負(fù)責(zé)加載 JDK 核心類庫,奠定 Java 程序運(yùn)行的基礎(chǔ),由 JVM 的底層代碼(C/C++,如 HotSpot 虛擬機(jī))實(shí)現(xiàn),無法在 Java 代碼中直接獲取其對象(調(diào)用 getClassLoader() 返回 null)。
加載范圍:加載 JDK 核心類庫
- JDK 8 及之前:加載
<JAVA_HOME>/jre/lib目錄下的核心 JAR 包(如rt.jar、charsets.jar、sunrsasign.jar等),這些 JAR 是 JVM 識別的「核心類庫」(非該目錄下的任意 JAR 不會被加載); - JDK 9+(模塊化):移除了
rt.jar等打包方式,核心類以「模塊」形式存儲在<JAVA_HOME>/lib/modules(模塊化鏡像文件),啟動類加載器負(fù)責(zé)加載java.base等核心模塊(如java.lang、java.io所屬的模塊)。
擴(kuò)展(Extension)類加載器:
擴(kuò)展類加載器是啟動類加載器的子類,Java語言編寫,由sun.misc.Launcher$ExtClassLoader實(shí)現(xiàn),父類加載器為啟動類加載器,負(fù)責(zé)加載標(biāo)準(zhǔn)核心Java類的擴(kuò)展。
擴(kuò)展類加載器從JDK擴(kuò)展目錄(通常是$JAVA_HOME/lib/ext目錄)或java.ext.dirs系統(tǒng)屬性中指定的任何其他目錄進(jìn)行自動加載。
系統(tǒng)/應(yīng)用(System)類加載器:
系統(tǒng)類加載器負(fù)責(zé)將所有應(yīng)用程序級類加載到JVM中。它加載在類路徑環(huán)境變量,-classpath或-cp命令行選項(xiàng)中找到的文件。它是擴(kuò)展類加載器的子類。
自定義類加載器:
在大多數(shù)情況下,內(nèi)置的三種類加載器就足夠了。但是,在需要從本地硬盤驅(qū)動器或網(wǎng)絡(luò)中加載類的情況下,需要打破雙親委派機(jī)制,則可能需要使用自定義類加載器。
下面介紹類加載器的加載機(jī)制--雙親委派機(jī)制。
雙親委派機(jī)制:

從圖中可以清晰的看到雙親委派機(jī)制的具體過程:加載某個類時會先層層委托給父加載器尋找目標(biāo)類加載,如果父加載器在自己的加載類路徑下都找不到目標(biāo)類,嘗試向下加載。比如我們自己隨便寫的User類,最先會找應(yīng)用程序類加載器加載,應(yīng)用程序類加載器會先委托擴(kuò)展類加載器加載,擴(kuò)展類加載器再委托啟動類加載器,頂層啟動類加載器在自己的類加載路徑里找了半天沒找到User類,則向下退回加載User類的請求,擴(kuò)展類加載器收到回復(fù)就自己加載,在自己的類加載路徑里找了半天也沒找到User類,又向下退回User類的加載請求給應(yīng)用程序類加載器,應(yīng)用程序類加載器于是在自己的類加載路徑里找User類,結(jié)果找到了就自己加載了。雙親委派機(jī)制說簡單點(diǎn)就是,先找父親加載,不行再由兒子自己加載。
我們來看一下loadclass的源碼:
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}此方法負(fù)責(zé)加載給定名稱參數(shù)的類。name參數(shù)為類的全限定名。先查找是否已加載該類,若無,則判斷有無父類加載器parent,嘗試給父類加載器去loadclass,其實(shí)就是一個遞歸的順序。
為什么要雙親委派機(jī)制:
沙箱安全機(jī)制:自己寫的java.lang.String.class類不會被加載,這樣便可以防止
核心API庫被隨意篡改( 可以自己寫一個String 類 會發(fā)現(xiàn)報錯 )
避免類的重復(fù)加載:當(dāng)父親已經(jīng)加載了該類時,就沒有必要子ClassLoader再加
載一次,保證被加載類的唯一性
打破雙親委派機(jī)制示例:
1、tomcat:tomcat就是屬于一個web 程序,可能部署倆個應(yīng)用程序,不同的應(yīng)用程序可能會依賴一個第三方類庫的不同版本,不能要求同一個類庫在同一個服務(wù)器只有一份,因此要保證每個應(yīng)用程序的類庫都是獨(dú)立的,保證相互隔離,就需要打破雙親委派機(jī)制。
2、熱部署:熱部署要求 “修改類 / JSP 后,無需重啟應(yīng)用即可生效”,而雙親委派下,類加載器加載類后,該類會被 JVM 緩存,類加載器不銷毀,類就無法重新加載。因此需要自定義類加載器 + 銷毀重建。
總結(jié)
到此這篇關(guān)于Java中類加載器的文章就介紹到這了,更多相關(guān)Java類加載器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC 響應(yīng)數(shù)據(jù)和結(jié)果視圖從環(huán)境搭建到實(shí)戰(zhàn)全解析
SpringMVC中,Controller方法的返回值決定了響應(yīng)方式,本文詳細(xì)講解了SpringMVC的開發(fā)環(huán)境搭建、Controller方法返回值分類、轉(zhuǎn)發(fā)與重定向機(jī)制,以及JSON異步交互的實(shí)現(xiàn),本文介紹SpringMVC 響應(yīng)數(shù)據(jù)和結(jié)果視圖從環(huán)境搭建到實(shí)戰(zhàn)全解析,感興趣的朋友跟隨小編一起看看吧2025-11-11
Java使用多線程批次查詢大量數(shù)據(jù)(Callable返回?cái)?shù)據(jù))方式
今天給大家分享Java使用多線程批次查詢大量數(shù)據(jù)(Callable返回?cái)?shù)據(jù))方式,多線程有好幾種方式,今天說的方式比較好,實(shí)現(xiàn)Callable<> 這種方式能返回查詢的數(shù)據(jù),加上Future異步獲取方式,查詢效率大大加快,感興趣的朋友一起看看吧2023-11-11
java開發(fā)使用StringUtils.split避坑詳解
這篇文章主要為大家介紹了java開發(fā)使用StringUtils.split避坑詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
Java獲取漢字拼音的全拼和首拼實(shí)現(xiàn)代碼分享
這篇文章主要介紹了Java獲取漢字拼音的全拼和首拼實(shí)現(xiàn)代碼分享,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-06-06
SpringBoot整合RabbitMQ實(shí)現(xiàn)交換機(jī)與隊(duì)列的綁定
這篇文章將通過幾個實(shí)例為大家介紹一些SpringBoot中RabbitMQ如何綁定交換機(jī)(交換器)與隊(duì)列,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-05-05
Java實(shí)現(xiàn)時間戳轉(zhuǎn)代碼運(yùn)行時長
這篇文章主要為大家詳細(xì)介紹了如何使用Java實(shí)現(xiàn)時間戳轉(zhuǎn)代碼運(yùn)行時長功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-06-06

