基於《深入理解 Java 虛擬機》學習筆記,作者在 Mac OS X + JDK 1.8.0_92 環境下(-XX:+UseSerialGC 串行收集器)設計三個實驗,探究 Eden 區初始佔用、Minor GC 後殘留內存、大對象直接進老年代等核心機制。本文結合 GC 日誌深度解析,還原 JVM 內存分配真相。
-verbose:gc -XX:+PrintGCDetails -XX:+UseSerialGC
-Xms20M -Xmx20M -Xmn10M // 固定堆 20M,新生代 10M
Heap
def new generation total 9216K, used 1164K
eden space 8192K, 14% used
結論:Eden 區非零佔用是 JVM 運行時基礎設施初始化的必然結果,非業務對象佔用。
byte[] a1..a6 = new byte[1MB]; // 分配 6MB 對象
-XX:SurvivorRatio=3 -XX:TenuringThreshold=1
[GC (Allocation Failure) DefNew: 6029K->1354K(8192K), 0.0033 secs]
6029K->5450K(18432K)
結論:Eden 非零是 GC 後新分配的對象;堆減少的是 GC 內部臨時對象,非業務數據。
byte[] a1=2MB, a2=2MB, a3=2MB, a4=4MB; // 總 10MB,新生代 10MB
-XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15
[GC (Allocation Failure) [DefNew (promotion failed): 7144K->7144K(9216K)]
[Tenured: 4096K->4096K(6144K)] 7144K->6473K(15360K)]
結論:非自動設置大對象閾值,而是 擔保失敗觸發的逃生機制。大對象直接進老年代由 -XX:PretenureSizeThreshold 顯式控制,默認 0(不生效)。本例是分配失敗後的應急策略。
| 現象 | 根本原因 | 關鍵參數 |
|---|---|---|
| 啟動即佔用 Eden | JVM 運行時基礎設施 + TLAB | -XX:+PrintTLAB |
| Minor GC 後 Eden 非零 | GC 後續分配新對象 | -XX:+PrintGCDetails |
| 堆減少但業務對象未回收 | 回收 GC 臨時對象 | -XX:+PrintGCApplicationStoppedTime |
| 大對象直接進老年代 | 擔保失敗逃生機制 | -XX:HandlePromotionFailure |
-XX:+PrintTenuringDistribution 觀察對象年齡分佈與動態閾值。jstat -gcutil pid 1000 或 VisualVM 動態監控各代佔用。-XX:+UnlockDiagnosticVMOptions -XX:+PrintGCApplicationConcurrentTime 分析應用線程與 GC 交互。JVM 內存分配看似複雜,實則遵循「優先 Eden、大對象直入老年代、長期存活進老年代、空間擔保失敗觸發 Full GC」幾條鐵律。通過參數控制與日誌分析,所有行為皆可預測與調優。