小米二面:JVM 觸發類加載的條件有哪些?我說 new 的時候加載,然后他對我笑了笑......
Java 虛擬機(JVM)中,類的加載并不是隨意發生的,而是由特定的觸發條件決定的。什么時候加載?什么時候初始化?
這是我們必須要搞清楚的問題,尤其在復雜的應用中,弄懂類加載的時機能幫助我們避免一些潛在的性能問題和運行時錯誤。
在本節中,我們將詳細探討類加載的時機、主動和被動引用的區別,以及常見的類加載觸發條件。
類加載生命周期
類加載的生命周期包括:加載(Loading)、鏈接(Linking) 和 初始化(Initialization)。而其中,初始化階段是決定類是否被真正加載的關鍵。
JVM 在什么時候啟動類加載過程呢?
主要分為主動引用和被動引用兩種情況。我們分別看看這兩種情況在什么條件下會觸發類加載。
主動引用
主動引用是指程序顯式地使用某個類,從而觸發類的加載和初始化。根據《Java 虛擬機規范》,以下六種情況會觸發類的主動引用,也就是觸發類加載的條件!
1. 創建類的實例
當你使用 new 關鍵字創建一個類的實例時,JVM 會立即加載并初始化該類。
// 觸發 MyClass 的加載和初始化
MyClass obj = new MyClass();
初始化流程:
- 分配內存給 MyClass 的實例對象。
- 加載 MyClass 類的字節碼,并執行靜態代碼塊和靜態變量賦值操作。
2. 訪問類的靜態字段或靜態方法
訪問類的靜態字段或靜態方法時,也會觸發類的加載和初始化。
// 觸發 MyClass 的加載
System.out.println(MyClass.staticVar);
// 觸發 MyClass 的加載
MyClass.staticMethod();
常量不會觸發類加載:如果靜態字段是 final 修飾的常量,它在編譯期已存入常量池,因此不會觸發類加載。
System.out.println(MyClass.FINAL_CONSTANT); // 不觸發類加載
3. 反射
通過反射調用類時,也會觸發類加載。
Class<?> clazz = Class.forName("com.example.MyClass"); // 觸發 MyClass 的加載
4. 初始化類的子類時,先初始化父類
當初始化一個類時,如果它的父類尚未初始化,JVM 會先初始化父類。
public class Parent {
static {
System.out.println("父類初始化");
}
}
public class Child extends Parent {
static {
System.out.println("子類初始化");
}
}
// 先輸出"父類初始化",再輸出"子類初始化"
Child obj = new Child();
5. 擬機啟動時,初始化 main 方法所在的類
虛擬機啟動時,main 方法所在的類是程序的入口類,會被優先加載和初始化。
public static void main(String[] args) {
System.out.println("主類加載");
}
6. 動態語言支持
在 Java 7 引入的 java.lang.invoke 包中,當 MethodHandle 最終指向的類需要初始化時,也會觸發類的加載。
MethodHandle handle = MethodHandles.lookup().findStatic(MyClass.class, "staticMethod", MethodType.methodType(void.class));
handle.invoke(); // 可能觸發 MyClass 的加載
被動引用:不觸發類加載
與主動引用相對,被動引用是指訪問類的某些特性時不會觸發類的加載和初始化。以下是幾種典型的被動引用場景。
1. 通過子類引用父類的靜態字段
如果子類只引用父類的靜態字段,JVM 只會初始化父類,而不會初始化子類。
示例
// 只觸發 Parent 的加載,不觸發 Child 的加載
System.out.println(Child.staticVar);
2. 訪問編譯期常量
訪問 final 修飾的編譯期常量,不會觸發類的加載。
// 不觸發 MyClass 的加載
System.out.println(MyClass.FINAL_CONSTANT);
3. 通過數組定義類引用
通過數組引用一個類,不會觸發該類的加載。
// 不觸發 MyClass 的加載
MyClass[] array = new MyClass[10];
碼哥,為什么需要關注類加載的時機?
- 避免類的過早加載:過早加載可能導致不必要的內存消耗,尤其在大型應用中。
- 延遲加載(Lazy Loading):通過延遲加載,可以在真正需要時才加載類,減少啟動時間。
- 減少類加載沖突:在模塊化或插件化的應用中,合理安排類加載順序有助于避免類沖突和類加載死鎖問題。