JVM 為什么需要類加載機制?深入淺出 JVM 類加載原理
類加載機制是什么?
在 Java 中,類加載機制是 Java 虛擬機(JVM)將 .class 文件加載到內(nèi)存并轉(zhuǎn)化為可以運行的 Class 對象的過程。簡單來說,類加載機制是讓“代碼變?yōu)楝F(xiàn)實”的第一步!
你可能會問,為什么需要類加載機制? 因為 Java 是一門 動態(tài)語言,類可以在運行時加載、鏈接和初始化,這種靈活性讓 Java 能夠?qū)崿F(xiàn)跨平臺運行、高效的內(nèi)存管理和模塊化架構。
類加載的三個階段
根據(jù)《Java 虛擬機規(guī)范》,類的生命周期包括以下三個主要階段:加載、鏈接 和 初始化。
而其中鏈接又分為三個子階段:驗證(Verification)、準備(Preparation)、解析(Resolution)。
圖片
我們逐一拆解這些階段的工作原理和流程。
加載(Loading)
Chaya:類加載階段作用是什么?非要加載嗎?
主要是使用 "類加載器" 將本地或者遠程網(wǎng)絡中的字節(jié)碼文件,通過讀字節(jié)流的方式加載到 Java 虛擬機內(nèi)存中。在加載階段中 Java 虛擬機主要完成以下三件事情:
- ① 通過一個類的全限定名稱來獲取定義此類的二進制字節(jié)流。
- ② 將這個字節(jié)流所代表的靜態(tài)存儲結構轉(zhuǎn)化為方法區(qū)的運行時數(shù)據(jù)結構。
- ③ 在內(nèi)存中生成一個代表這個類的
java.lang.Class對象,作為方法區(qū)中這個類的各種數(shù)據(jù)的訪問入口。
加載是類加載的第一步,JVM 需要完成以下任務:
圖片
- 讀取 Class 文件:通過類的全限定名找到對應的
.class文件。 - 轉(zhuǎn)換為 JVM 可識別的結構:將 Class 文件的二進制數(shù)據(jù)轉(zhuǎn)換為 JVM 的運行時數(shù)據(jù)結構。
- 創(chuàng)建 Class 對象:在內(nèi)存中創(chuàng)建
java.lang.Class對象,作為該類的入口。
示例。
Class<?> clazz = Class.forName("com.example.MyClass");這段代碼會觸發(fā) MyClass 的加載,將其 .class 文件讀取到內(nèi)存中,并生成 Class 對象。
鏈接(Linking)
鏈接 是將 Class 文件中的符號引用解析為直接引用的過程,分為以下三個子階段:
- 驗證(Verification)確保 Class 文件的字節(jié)碼格式和內(nèi)容符合 JVM 的規(guī)范。
驗證文件格式:Class 文件是否以 0xCAFEBABE 開頭。
驗證字節(jié)碼:指令是否符合 JVM 規(guī)范,數(shù)據(jù)類型是否匹配。
- 準備(Preparation)為類的靜態(tài)變量分配內(nèi)存,并設置默認值。
例如:static int a = 10; 在準備階段,a 的初始值是 0。
- 解析(Resolution)將符號引用替換為內(nèi)存地址的直接引用。
符號引用:java.lang.String
直接引用:指向 String 類在內(nèi)存中的地址。
驗證階段 (Verification)
驗證階段的主要目的是對字節(jié)碼字節(jié)流進行校驗,判斷其內(nèi)容是否符合當前虛擬機的規(guī)范,以確保被加載的代碼運行后不會對虛擬機造成損害。
大多數(shù)虛擬機大致都會對 文件格式、元數(shù)據(jù)、字節(jié)碼、符號引用 幾項內(nèi)容進行校驗。
文件格式驗證
文件格式驗證主要是對 字節(jié)流格式 進行校驗,判斷其是否符合字節(jié)碼文件格式規(guī)范,并且還要判斷其是否可以運行在當前版本的虛擬機中。比如:
序號 | 描述 |
1 | 驗證是否以 0XCAFEBABE 開頭 |
2 | 驗證主、次版本號,是否包含在當前虛擬機支持的版本范圍內(nèi) |
3 | 驗證字節(jié)碼常量池中的常量類型,是否都被虛擬機所支持 |
4 | 驗證指向常量的各種索引值,是否有指向不存在的常量或不符合類型的常量 |
5 | 驗證 CONSTANT_Utf8_info 類型常量中,是否有不符合 UTF-8 編碼的數(shù)據(jù) |
6 | 驗證字節(jié)碼文件中各個部分及文件本身,是否有被刪除或附加的其他信息 |
文件格式驗證的主要目的其實就是為了保證加載的字節(jié)碼可以被正確地解析并存儲在方法區(qū)內(nèi)。
元數(shù)據(jù)驗證
元數(shù)據(jù)驗證主要是對 字節(jié)碼 中的 元數(shù)據(jù)信息 進行語法校驗,避免存在不符合 Java 語法規(guī)范的元數(shù)據(jù)信息。比如:
序號 | 描述 |
1 | 驗證當前類的父類是否繼承了不允許被繼承的類,比如被 final 修飾的類 |
2 | 驗證當前類是否有父類,一般情況下除了 java.lang.Object 外,所有的類都應當有父類 |
3 | 驗證如果當前類不是抽象類,則當前類是否實現(xiàn)了其父類或接口之中要求實現(xiàn)的所有方法 |
4 | 驗證當前類中的字段或方法是否與父類有沖突,比如當前類覆蓋了父類的 final 字段,或者當前類實現(xiàn)的方法參數(shù)都一致,但返回值的類型卻不同,導致不符合方法重載規(guī)則等情況 |
字節(jié)碼驗證
字節(jié)碼驗證主要是對 數(shù)據(jù)流 和 控制流 進行分析,以確保其語法合規(guī)且符合邏輯。
符號引用驗證
符號引用驗證主要對 字節(jié)碼常量池 中 常量 的各種 符號引用 進行校驗,確保當前類引用到的其它類或者方法是真實存在且有權限訪問的。如果符號引用中關聯(lián)的類無法在系統(tǒng)中查找到,就會拋出 NoClassDefFoundError 錯誤,如果符號引用中關聯(lián)的方法無法找到,則會拋出 NoSuchMethodError 錯誤。
準備階段 (Preparation)
準備階段主要是用于對類或接口中的 "靜態(tài)變量" 分配內(nèi)存空間,以及對變量設置默認的初始值。
準備階段和初始化階段,這兩個階段都是用于對靜態(tài)變量設置值,概念上容易混淆,所以這里需要特別說明一下,準備階段只是對靜態(tài)變量設置初始默認值,而真正賦值操作是在初始化階段完成的。
例如,下面示例代碼在執(zhí)行時:
public class A {
static int test = 999;
}- 準備階段會對變量 test 設置默認值
0; - 初始化階段會對變量 test 賦予初始值
999;
解析階段 (Resolution)
解析階段主要是用于將 字節(jié)碼常量池 中的 符號引用 替換為 直接引用 的過程。
- 符號引用 (Symbolic References): 符號引用就是用于描述引用目標的一組符號,它可以是任何形式的字面量 (只要符合 Java 虛擬機規(guī)范)。
- 直接引用 (Direct References): 直接引用可以是直接指向目標的指針、相對偏移量,或者是一個能間接定位到目標的句柄。
初始化(Initialization)
- 初始化階段是類加載的最后一步,也是最重要的階段。此階段會執(zhí)行靜態(tài)變量的賦值操作和靜態(tài)代碼塊。
初始化的觸發(fā)條件:
類的初始化順序
先初始化父類。
再初始化當前類的靜態(tài)變量和靜態(tài)代碼塊。
使用 new 關鍵字實例化對象時。
訪問類的靜態(tài)字段或靜態(tài)方法時。
使用反射調(diào)用類時。
唐二婷:初始化階段有啥用?可以談戀愛嗎?
初始化階段主要是執(zhí)行 類構造器 方法 <clinit>(),該方法不需要定義,代碼在經(jīng)過 Javac 編譯器編譯時,會自動收集類中的所有 類變量 的賦值動作和 靜態(tài)代碼塊 中的語句,對這些代碼進行合并,形成類構造器 <clinit>() 。
在執(zhí)行類構造器 <clinit>() 時,會對類中的 類變量 和 靜態(tài)代碼塊 進行初始化賦值操作,如果該類存在父類,則會先執(zhí)行父類中的類構造器 <clinit>(),對父類中的 類變量 和 靜態(tài)代碼塊 進行初始化。
示例如下。
public class FatherCLass {
public static int number;
static {
System.out.println(number);
System.out.println("父類 static{} 初始化");
}
}子類:
public class SubInitialization extends FatherCLass {
static{
// number 屬于父類的屬性,這里要能執(zhí)行成功,說明父類已經(jīng)加載
number = 100;
System.out.println("子類 static{} 初始化");
}
public static void main(String[] args) {
System.out.println(number);
}
}執(zhí)行時輸出如下:
0
父類 static{} 初始化
子類 static{} 初始化
100





























