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

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

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


啟動(Bootstrap)類加載器:
系統(tǒng)中最頂層、最核心的加載器,是 JVM 與生俱來的一部分,負責加載 JDK 核心類庫,奠定 Java 程序運行的基礎,由 JVM 的底層代碼(C/C++,如 HotSpot 虛擬機)實現(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(模塊化鏡像文件),啟動類加載器負責加載java.base等核心模塊(如java.lang、java.io所屬的模塊)。
擴展(Extension)類加載器:
擴展類加載器是啟動類加載器的子類,Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現(xiàn),父類加載器為啟動類加載器,負責加載標準核心Java類的擴展。
擴展類加載器從JDK擴展目錄(通常是$JAVA_HOME/lib/ext目錄)或java.ext.dirs系統(tǒng)屬性中指定的任何其他目錄進行自動加載。
系統(tǒng)/應用(System)類加載器:
系統(tǒng)類加載器負責將所有應用程序級類加載到JVM中。它加載在類路徑環(huán)境變量,-classpath或-cp命令行選項中找到的文件。它是擴展類加載器的子類。
自定義類加載器:
在大多數(shù)情況下,內(nèi)置的三種類加載器就足夠了。但是,在需要從本地硬盤驅(qū)動器或網(wǎng)絡中加載類的情況下,需要打破雙親委派機制,則可能需要使用自定義類加載器。
下面介紹類加載器的加載機制--雙親委派機制。
雙親委派機制:

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

