| 類型 | 位置 | 生命週期 | 默認值 | 初始化時機 |
|---|---|---|---|---|
| 實例變量 (非 static) | 堆 (對象內部) | 隨對象 | 有 (0/null/false) | 構造器執行前 (聲明處/塊/構造器) |
| 類變量 (static) | 元空間 (Class 對象) | 隨類加載 | 有 | 類加載準備/初始化階段 (聲明處/靜態塊) |
| 局部變量 | 棧 (幀內) | 隨方法 | 無 (必須顯式賦值) | 執行到聲明語句時 |
public class InitOrder {
// 1. 聲明賦值
int a = 10;
// 2. 實例初始化塊 (按源碼順序)
{ a = 20; System.out.println("塊1: a=" + a); }
// 3. 構造器
public InitOrder() { a = 30; System.out.println("構造器: a=" + a); }
// 4. 另一塊 (順序在構造器前)
{ a = 40; System.out.println("塊2: a=" + a); }
}
執行順序:聲明賦值 → 塊1 → 塊2 → 構造器。
編譯器行為:將聲明賦值和塊代碼按順序複製到每個構造器開頭 (super() 之後)。
class Trap {
static Trap INSTANCE = new Trap(10); // 1. 創建對象,調用構造器
static int VALUE = 20; // 2. 賦值 20
int value;
Trap(int v) { value = VALUE; } // 構造器執行時 VALUE 還是 0 (默認值)!
}
結果:`INSTANCE.value == 0`。
修復:調換聲明順序,或用 `final static int VALUE = 20` (編譯期常量,宏替換)。
class Father { int age = 50; void info() { System.out.println(age); } }
class Son extends Father { int age = 24; void info() { System.out.println(age); } }
Father f = new Son(28);
System.out.println(f.age); // 50 (編譯期類型 Father 決定訪問 Father.age)
f.info(); // 24 (運行期類型 Son 決定調用 Son.info)
鐵律:變量看編譯類型,方法看運行類型。
內存實相:`new Son()` 在堆分配一塊連續內存,包含 Father.age 和 Son.age 兩個槽位。`f` 引用指向該內存起始地址。
| 修飾目標 | 規則 | 宏替換條件 |
|---|---|---|
| 類 | 不可繼承 | 無 |
| 方法 | 不可重寫 (private 同效) | 無 |
| 變量 (基本類型) | 引用不可變,賦值一次 | 編譯期常量 (字面量/常量表達式) |
| 變量 (引用類型) | 引用不可變,對象內容可變 | 無 (引用地址非編譯期常量) |
class Consts {
static final String A = "Hello";
static final String B = "World";
static final String C = A + B; // 編譯期常量表達式 → 宏替換為 "HelloWorld"
}
反編譯可見:調用處直接嵌入 "HelloWorld",無訪問 `Consts.C` 字節碼指令。
| 方案 | 原理 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| Instrumentation.getObjectSize | JVM 內建 API (需 javaagent) | 準確、含對象頭、無遞歸 | 需啟動參數、不含引用對象 | 生產監控、緩存大小限制 |
| Runtime 内存差值法 | gc() 前後 freeMemory 差值 | 無依賴、含引用對象(視GC) | 極不準確、受 GC 干擾大 | 粗略估算、教學演示 |
| 反射遞歸計算 | 遍歷字段、數組元素、對象頭估算 | 無依賴、可含引用對象 | 複雜、需處理循環引用、JVM 實現相關 | 離線分析、堆轉儲分析 |
| JOL (Java Object Layout) | 開源庫,基於 Unsafe/Instrumentation | 專業、詳盡佈局、支援遞歸 | 第三方依賴 | 深度分析、性能調優 |