類加載全流程
- 加載:類加載器讀取
.class 字節碼 → 方法區生成 Class 對象(反射入口)。
- 連接:
- 驗證:字節碼格式、語義合法性。
- 準備:為 靜態字段 分配內存並賦 默認值(0/false/null)。
- 解析:常量池符號引用 → 直接引用(可延遲到首次使用)。
- 初始化:執行
<clinit>()(靜態變量賦值語句 + 靜態代碼塊,按源碼順序),父類先於子類。
主動使用觸發初始化(6 種)
new 創建實例
- 調用靜態方法
- 讀/寫 非常量 靜態字段(
final static 基本類型/字符串為常量,編譯期常量池化,不觸發)
- 反射
Class.forName()、Method.invoke()
- 初始化子類(觸發父類初始化)
- 啟動主類(含
main)
代碼驗證:初始化順序
class Base {
int a; static int b; static int c = 1;
static { b = 2; c = 3; System.out.println("Base static block"); }
}
class Derived extends Base {
int d; static int e = 4;
static { e = 5; System.out.println("Derived static block"); }
}
public class Demo {
static int f; static { f = 100; System.out.println("Demo static block, f=" + f); }
public static void main(String[] args) {
System.out.println("main start");
new Derived().display();
}
}
// 輸出順序:
// Demo static block, f=100
// Base static block
// Derived static block
// main start
// ...
關鍵細節
- 接口初始化:不要求父接口初始化,只有真正使用到的靜態字段才觸發該接口初始化。
final static 常量(編譯期常量)直接內聯到調用處字節碼,不觸發類初始化。
- 類加載器雙親委派:Bootstrap → Extension → Application → Custom,保證核心類安全。
Class.forName("xxx", true, loader) 參數 true 表示初始化,false 僅加載。
實戰意義
- 單例模式靜態內部類寫法利用類初始化線程安全特性實現延遲加載。
- JDBC 驅動註冊
Class.forName("com.mysql.jdbc.Driver") 觸發靜態塊向 DriverManager 註冊。
- 排查
NoClassDefFoundError vs ClassNotFoundException:前者初始化失敗,後者加載找不到。