深入理解Java虛擬機(jī)-類加載機(jī)制
類加載過程
加載(Loading)
- 將類的字節(jié)碼文件(.class文件)加載到內(nèi)存,并生成對應(yīng)的Class對象(存儲(chǔ)在方法區(qū))
- 查找字節(jié)碼:通過類的全限定名(如java.lang.String)定位字節(jié)碼文件
- 讀取字節(jié)碼:將字節(jié)碼轉(zhuǎn)換為二進(jìn)制流
- 生成Class對象:在方法區(qū)中創(chuàng)建類的Class對象(后續(xù)反射操作的基礎(chǔ))Class<?> clazz = Class.forName("com.example.MyClass"); // 觸發(fā)加載階段
驗(yàn)證(Verification)
- 確保字節(jié)碼符合JVM規(guī)范,防止惡意代碼或錯(cuò)誤字節(jié)碼危害JVM安全
- 文件格式驗(yàn)證:檢查魔數(shù)(0xCAFEBABE)和版本號(hào)是否合法,驗(yàn)證常量池中的常量類型是否有效
- 元數(shù)據(jù)驗(yàn)證:檢查類是否有父類(除Object外)、是否繼承final類、字段/方法是否與父類沖突若一個(gè)類嘗試?yán)^承final類(如String),驗(yàn)證階段會(huì)拋出java.lang.VerifyError
- 字節(jié)碼驗(yàn)證:確保方法體的字節(jié)碼不會(huì)導(dǎo)致JVM崩潰(如操作數(shù)棧溢出、跳轉(zhuǎn)到不存在的指令)
- 符號(hào)引用驗(yàn)證:檢查符號(hào)引用(如類名、方法名)是否合法,確保解析階段能正確綁定
準(zhǔn)備(Preparation)
- 為類的靜態(tài)變量(類變量)分配內(nèi)存,并設(shè)置初始值(零值)
解析(Resolution)
- 將常量池中的符號(hào)引用轉(zhuǎn)換為直接引用(內(nèi)存地址或偏移量)
- 符號(hào)引用:一組符號(hào)描述目標(biāo)(如
java/lang/Object
) - 直接引用:指向目標(biāo)的指針、偏移量或句柄
- 類/接口解析:將類名符號(hào)引用轉(zhuǎn)換為對應(yīng)的
Class
對象 - 字段解析:將字段名轉(zhuǎn)換為內(nèi)存中的偏移量
- 方法解析:將方法名轉(zhuǎn)換為方法入口地址
- 解析階段可能在初始化之后觸發(fā)(如動(dòng)態(tài)綁定)
初始化(Initialization)
- 執(zhí)行類的初始化代碼(<clinit>()方法),為靜態(tài)變量賦真實(shí)值,執(zhí)行靜態(tài)代碼塊
- 觸發(fā)條件創(chuàng)建類的實(shí)例(new)訪問類的靜態(tài)變量(非常量)或靜態(tài)方法反射調(diào)用(如Class.forName())初始化子類時(shí),父類需先初始化JVM啟動(dòng)時(shí)指定的主類(包含main()的類)
- 初始化順序:父類的<clinit>()先于子類執(zhí)行,靜態(tài)變量賦值和靜態(tài)代碼塊按代碼順序執(zhí)行
- 若多個(gè)線程同時(shí)初始化一個(gè)類,JVM會(huì)保證同步(僅一個(gè)線程執(zhí)行<clinit>())
類加載總結(jié)
加載 |
文件 |
對象 | 查找字節(jié)碼,生成內(nèi)存結(jié)構(gòu) |
驗(yàn)證 | 字節(jié)碼 | 合法字節(jié)碼 | 檢查格式、元數(shù)據(jù)、字節(jié)碼邏輯 |
準(zhǔn)備 | 靜態(tài)變量符號(hào)引用 | 靜態(tài)變量內(nèi)存分配(零值) | 分配內(nèi)存,設(shè)置默認(rèn)值 |
解析 | 符號(hào)引用 | 直接引用 | 綁定類、字段、方法的內(nèi)存地址 |
初始化 | 靜態(tài)變量和代碼塊 | 類完全可用 | 執(zhí)行
,賦真實(shí)值,執(zhí)行靜態(tài)代碼塊 |
常見問題與解決方案
- 類加載失?。涸颍侯惵窂藉e(cuò)誤、字節(jié)碼損壞、版本不兼容解決:檢查-classpath配置,確認(rèn)類文件完整性
- 靜態(tài)代碼塊死鎖:原因:多線程初始化時(shí),靜態(tài)代碼塊內(nèi)同步操作導(dǎo)致死鎖解決:避免在靜態(tài)代碼塊中使用復(fù)雜同步邏輯
- 類重復(fù)加載:原因:不同類加載器加載同一類解決:遵循雙親委派模型,避免自定義類加載器破壞機(jī)制
類加載器
類加載器的核心作用
- 加載字節(jié)碼:從文件系統(tǒng)、網(wǎng)絡(luò)、JAR包等來源讀取字節(jié)碼
- 類隔離:通過不同類加載器實(shí)現(xiàn)類的命名空間隔離(如Tomcat中的Web應(yīng)用)
- 動(dòng)態(tài)加載:支持運(yùn)行時(shí)加載類(如插件化、熱部署)
- 安全性控制:防止惡意代碼替換核心類(如
java.lang.String
)
啟動(dòng)類加載(Bootstrap ClassLoader)
- 職責(zé):加載JVM核心類庫(jre/lib目錄下的rt.jar、resources.jar等)Java 9+ 加載java.base(java.lang/java.util/java.io)、java.datatransfer、java.instrument
- 實(shí)現(xiàn):由C/C++編寫,是JVM的一部分,無Java類實(shí)例
- 訪問限制:無法在Java代碼中直接引用(getClassLoader()返回null)
擴(kuò)展類加載器(Extension ClassLoader)/平臺(tái)類加載器(Platform ClassLoader, Java 9+)
- 職責(zé):加載擴(kuò)展類庫(jre/lib/ext目錄下的JAR包)Java 9+ 加載java.sql、java.xml、java.logging、java.management
- 實(shí)現(xiàn):Java類sun.misc.Launcher$ExtClassLoader
- 父加載器:啟動(dòng)類加載器
應(yīng)用程序類加載器(Application ClassLoader)
- 職責(zé):加載用戶類路徑(
-classpath
或CLASSPATH
環(huán)境變量)下的類 - 實(shí)現(xiàn):Java類
sun.misc.Launcher$AppClassLoader
- 父加載器:擴(kuò)展類加載器
- 默認(rèn)類加載器:
ClassLoader.getSystemClassLoader()
返回此加載器
雙親委派模型(Parent Delegation Model)
- 委派父加載器:優(yōu)先讓父加載器嘗試加載
- 父加載器失敗:若父加載器無法加載,自己嘗試加載。
- 最終失?。喝羲屑虞d器無法加載,拋出ClassNotFoundException
- 雙親委派的優(yōu)勢避免類重復(fù)加載:父加載器加載的類,子加載器不會(huì)重復(fù)加載保護(hù)核心類庫:防止用戶自定義類覆蓋核心類(如自定義java.lang.Object
自定義類加載器
- 繼承ClassLoader:重寫findClass()方法
- 加載字節(jié)碼:從自定義路徑(如網(wǎng)絡(luò)、加密文件)讀取字節(jié)碼
- 定義類:調(diào)用defineClass()生成Class對象
- 使用場景熱替換:動(dòng)態(tài)加載修改后的類(如調(diào)試環(huán)境)加密類加載:加載加密的字節(jié)碼文件模塊隔離:不同模塊使用獨(dú)立類加載器(如Tomcat的Web應(yīng)用)
Tomcat的類加載器
- 層級(jí)結(jié)構(gòu):Common ClassLoader:加載Tomcat和Web應(yīng)用共享的類WebApp ClassLoader:每個(gè)Web應(yīng)用獨(dú)立,加載WEB-INF/classes和WEB-INF/libJSP ClassLoader:動(dòng)態(tài)加載JSP編譯后的類,支持熱替換
- 隔離機(jī)制:不同Web應(yīng)用的類加載器相互隔離,避免類沖突
Spring的動(dòng)態(tài)代理
- 場景:為接口生成代理類。
- 類加載器:使用
AppClassLoader
加載代理類,或通過Thread.currentThread().getContextClassLoader()
獲取
OSGi模塊化
- 機(jī)制:每個(gè)Bundle(模塊)有自己的類加載器,按需動(dòng)態(tài)加載依賴
- 優(yōu)勢:支持模塊熱插拔和版本共存
打破雙親委派
線程上下文類加載器(Thread Context ClassLoader)
- 背景:在SPI機(jī)制中,核心接口由啟動(dòng)類加載器加載,但實(shí)現(xiàn)類需由應(yīng)用類加載器加載
- 通過
Thread.currentThread().setContextClassLoader()
設(shè)置線程上下文類加載器 - 在SPI代碼中,使用
Thread.currentThread().getContextClassLoader()
加載實(shí)現(xiàn)類
自定義類加載器
- 背景:需要?jiǎng)討B(tài)加載類或?qū)崿F(xiàn)模塊化隔離
- 繼承
ClassLoader
,重寫loadClass()
或findClass()
方法,直接加載類而不委派父加載器
OSGi模塊化
- 背景:每個(gè)模塊(Bundle)需要獨(dú)立的類加載器,支持模塊熱插拔和版本共存
- 每個(gè)Bundle有自己的類加載器,按需加載依賴模塊
- 通過
Import-Package
和Export-Package
聲明模塊間的依賴關(guān)系
打破雙親委派的應(yīng)用場景
- SPI機(jī)制:JDBC、JNDI、JAXP等服務(wù)的實(shí)現(xiàn)類需由應(yīng)用類加載器加載,使用線程上下文類加載器加載實(shí)現(xiàn)類
- 熱部署:在開發(fā)或調(diào)試環(huán)境中,動(dòng)態(tài)替換已加載的類,自定義類加載器直接加載新版本的類,舊版本類由GC回收
- OSGI模塊化與插件化:不同模塊或插件需要獨(dú)立的類加載器,避免類沖突,每個(gè)模塊或插件使用獨(dú)立的類加載器