對象記憶體 = 對象頭 + 實例數據 + 對齊填充
普通對象頭 = 12 bytes,數組對象頭 = 16 bytes。
原則:同類型欄位聚合、寬類型優先、父類欄位在前、子類欄位在後。
對象總大小必須是 8 的倍數,不足補齊(對齊填充)。
// 未優化聲明順序
class BadOrder {
byte a; // 1
int b; // 4
boolean c; // 1
long d; // 8
Object e; // 4
}
// 計算:頭12 + a(1)+pad3 + b(4) + c(1)+pad7 + d(8) + e(4)+pad4 = 44 bytes
// 優化聲明順序(按分配策略排序)
class GoodOrder {
long d; // 8
int b; // 4
Object e; // 4
byte a; // 1
boolean c; // 1
}
// 計算:頭12 + d(8) + b(4) + e(4) + a(1) + c(1) + pad2 = 32 bytes
// 節省 12 bytes (27%)
class Parent {
long a; // 8
int b; // 4
int c; // 4
}
class Child extends Parent {
long d; // 8
}
// 佈局:頭12 + a(8) + b(4) + c(4) + d(8) = 36 → 對齊 40 bytes
class Parent {
byte a; // 1
}
class Child extends Parent {
long b; // 8
short c; // 2
byte d; // 1
}
// 父類佔用:頭12 + a(1) + pad3 = 16 bytes(已對齊)
// 子類接續:b(8) + c(2) + d(1) + pad5 = 16 bytes
// 總計:32 bytes
// 特殊規則:若子類首欄位為 long/double,且父類尾部有空閒空間(<8 bytes)
// JVM 會嘗試將子類的 long/double 壓入父類尾部空隙
class Parent2 {
byte a; // 1
}
class Child2 extends Parent2 {
long b; // 8 → 壓入父類 pad3 + pad4 = 7 bytes? 不夠,仍需新增 8 bytes
short c; // 2
byte d; // 1
}
// 實際佈局:頭12 + a(1) + c(2) + d(1) + pad2 + b(8) = 26 → 對齊 32 bytes
// 短類型 c/d 先填充父類空隙,long 獨佔 8 bytes
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.16</version>
</dependency>
// 使用
System.out.println(ClassLayout.parseClass(MyClass.class).toPrintable());
輸出示例:
MyClass object internals:
OFFSET SIZE TYPE DESCRIPTION
0 12 (object header)
12 8 long MyClass.d
20 4 int MyClass.b
24 4 Object MyClass.e
28 1 byte MyClass.a
29 1 boolean MyClass.c
30 2 (alignment padding)
Instance size: 32 bytes
| 主題 | 影響 |
|---|---|
| 關閉壓縮指針 (-XX:-UseCompressedOops) | 引用 8 bytes,對象頭 16 bytes,整體增大 30%~50% |
| 32 位 JVM | 引用 4 bytes,對象頭 8 bytes (Mark Word 4 + Klass 4),無壓縮指針概念 |
| 逃逸分析 + 標量替換 | 對象可能不在堆分配,而是拆解為棧上局部變數,大小為 0 |
| @Contended | 緩存行填充,避免偽共享,每欄位額外 128 bytes |
long[] ids 替代 List)。