在 JDK 25 中,Compact Object Headers(緊湊型物件標頭)已經正式成為 HotSpot JVM 的預設功能,並且不再需要使用 -XX:+UseCompactObjectHeaders 啟用這個功能。這是 JEP 519 的一部分,用途是進一步優化 Java 物件的記憶體佈局。在 JDK 24 中,這個功能曾作為實驗性功能引入,在 JDK 25 中轉為正式功能。
在 64 位架構的 HotSpot JVM 中,物件標頭的大小從原本的 12 至 16 字節(取決於 JVM 配置)縮減至 8 字節(64 位)。
減少記憶體佔用:物件標頭變小,整體記憶體佔用降低。
提高快取效率:更緊湊的記憶體佈局有助於提升 CPU 快取的命中率。
降低垃圾回收壓力:減少記憶體佔用量,有助於減少垃圾回收的次數。
提升部署密度:在容器化環境中,減少記憶體佔用量有助於提高部署密度。
Project Lilliput 的目標是將物件標頭的大小進一步縮小至 4 字節。然而,這樣的改變需要更深入的研究和測試,以確保不會影響 JVM 的穩定性和性能。
測試
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
/**
* JDK 25 Compact Object Header 功能測試
*
* 功能說明:
* Compact Object Header (JEP 450) 是 JDK 25 引入的實驗性特性
* 目的是減少物件頭的記憶體開銷,從傳統的 12-16 bytes 減少到 8 bytes
*
* 啟用方式:
* java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders YourClass
*
* 主要優勢:
* 1. 減少記憶體佔用(每個物件節省 4-8 bytes)
* 2. 提升快取效率(更好的資料局部性)
* 3. 適合大量小物件的應用場景
*/
public class CompactObjectHeaderTest {
private static final int OBJECT_COUNT = 1_000_000;
static class SmallObject {
private int id;
private String name;
public SmallObject(int id, String name) {
this.id = id;
this.name = name;
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("=== JDK 25 Compact Object Header 測試 ===\n");
// 檢查 JVM 參數
checkJVMFlags();
// 顯示初始記憶體狀態
System.out.println("\n--- 初始記憶體狀態 ---");
printMemoryUsage();
// 建立大量物件並測試
System.out.println("\n--- 建立 " + OBJECT_COUNT + " 個物件 ---");
List<SmallObject> objects = new ArrayList<>(OBJECT_COUNT);
long startTime = System.currentTimeMillis();
long startMemory = getUsedMemory();
for (int i = 0; i < OBJECT_COUNT; i++) {
objects.add(new SmallObject(i, "Object_" + i));
}
long endTime = System.currentTimeMillis();
long endMemory = getUsedMemory();
// 強制垃圾回收以獲得更準確的記憶體使用量
System.gc();
Thread.sleep(100);
long afterGCMemory = getUsedMemory();
// 顯示測試結果
System.out.println("\n--- 測試結果 ---");
System.out.println("建立時間: " + (endTime - startTime) + " ms");
System.out.println("記憶體增量 (建立後): " + formatBytes(endMemory - startMemory));
System.out.println("記憶體增量 (GC後): " + formatBytes(afterGCMemory - startMemory));
System.out.println("平均每個物件: " + formatBytes((afterGCMemory - startMemory) / OBJECT_COUNT));
// 顯示最終記憶體狀態
System.out.println("\n--- 最終記憶體狀態 ---");
printMemoryUsage();
// 物件頭大小估算
System.out.println("\n--- 物件頭分析 ---");
analyzeObjectHeader(afterGCMemory - startMemory);
// 保持物件存活
System.out.println("\n物件數量: " + objects.size());
System.out.println("\n提示: 使用 -XX:+UseCompactObjectHeaders 啟用壓縮物件頭");
System.out.println("比較指令: java -XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders CompactObjectHeaderTest");
}
private static void checkJVMFlags() {
System.out.println("JVM 版本: " + System.getProperty("java.version"));
System.out.println("JVM 供應商: " + System.getProperty("java.vendor"));
List<String> inputArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();
System.out.println("\nJVM 參數:");
for (String arg : inputArgs) {
System.out.println(" " + arg);
}
boolean hasCompactHeadersFlag = inputArgs.stream()
.anyMatch(arg -> arg.contains("UseCompactObjectHeaders"));
boolean isEnabled = inputArgs.stream()
.anyMatch(arg -> arg.contains("+UseCompactObjectHeaders"));
boolean isDisabled = inputArgs.stream()
.anyMatch(arg -> arg.contains("-UseCompactObjectHeaders"));
if (isEnabled) {
System.out.println("\n✓ Compact Object Headers 已明確啟用 (+UseCompactObjectHeaders)");
} else if (isDisabled) {
System.out.println("\n✗ Compact Object Headers 已明確禁用 (-UseCompactObjectHeaders)");
} else if (hasCompactHeadersFlag) {
System.out.println("\n? Compact Object Headers 參數已設定");
} else {
System.out.println("\n- Compact Object Headers 使用預設設定");
}
}
private static void printMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
long maxMemory = runtime.maxMemory();
System.out.println("已使用記憶體: " + formatBytes(usedMemory));
System.out.println("總分配記憶體: " + formatBytes(totalMemory));
System.out.println("最大可用記憶體: " + formatBytes(maxMemory));
System.out.println("可用記憶體: " + formatBytes(freeMemory));
}
private static long getUsedMemory() {
Runtime runtime = Runtime.getRuntime();
return runtime.totalMemory() - runtime.freeMemory();
}
private static String formatBytes(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
}
private static void analyzeObjectHeader(long totalMemory) {
// 估算物件頭大小
// 傳統物件頭: 12 bytes (32-bit) 或 16 bytes (64-bit 壓縮指標)
// Compact 物件頭: 8 bytes
long avgBytesPerObject = totalMemory / OBJECT_COUNT;
System.out.println("平均每個物件記憶體: " + avgBytesPerObject + " bytes");
System.out.println("\n理論估算:");
System.out.println(" - 傳統物件頭: 16 bytes (mark word + klass pointer)");
System.out.println(" - Compact 物件頭: 8 bytes");
System.out.println(" - int 欄位: 4 bytes");
System.out.println(" - String 參考: 4-8 bytes (壓縮指標)");
System.out.println(" - 對齊填充: 可能需要額外空間");
if (avgBytesPerObject <= 70) {
System.out.println("\n✓ 可能正在使用 Compact Object Headers");
} else {
System.out.println("\n✗ 可能使用傳統物件頭");
}
}
}
編譯後,用這兩種方式執行
# 禁用 Compact Object Headers
java -XX:-UseCompactObjectHeaders -Xms512m -Xmx512m CompactObjectHeaderTest > test_without_compact.log 2>&1
# 啟用
java -XX:+UseCompactObjectHeaders -Xms512m -Xmx512m CompactObjectHeaderTest > test_with_compact.log 2>&1
執行結果比較
傳統模式 - 平均每個物件: 78
壓縮模式 - 平均每個物件: 69
分析
傳統模式 (78 bytes):
├─ 物件頭:16 bytes (mark word 8 + klass pointer 8)
├─ int id:4 bytes
├─ String 參考:8 bytes (未壓縮指標)
└─ 對齊填充:~50 bytes (String 物件本身的開銷)
壓縮模式 (69 bytes):
├─ 物件頭:8 bytes (壓縮後)
├─ int id:4 bytes
├─ String 參考:8 bytes
└─ 對齊填充:~49 bytes
header 部分減少了 8 bytes,實際上節省 9 bytes
如果 application 是大量的小物件的集合,例如 cache,就啟用-XX:+UseCompactObjectHeaders
沒有留言:
張貼留言