運行參數 -Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC 執行特定分配代碼,按常規邏輯應觸發 4 次 Minor GC,實際卻出現 3 次 Minor GC + 2 次 Full GC。根因在於 Parallel Scavenge GC 的「悲觀策略」:在 Young GC 執行前後雙重預檢 Old 區剩餘空間,寧可提前觸發 Full GC 也要避免 Young GC 後大量對象晉升導致 Old 區 OOM。
List
boolean should_attempt_scavenge() {
size_t avg_promoted = padded_average_promoted_in_bytes(); // 歷史平均晉升量
size_t promotion_estimate = MIN2(avg_promoted, young_gen.used_in_bytes());
return promotion_estimate < old_gen.free_in_bytes(); // 不夠則放棄 YGC,直接 FGC
}
取「歷史平均晉升量」與「當前新生代已用量」較小值,若大於 Old 區剩餘空間,則跳過 YGC 直接觸發 Full GC。
bool should_full_GC(size_t old_free_in_bytes) {
return padded_average_promoted_in_bytes() > old_free_in_bytes; // 平均晉升量 > Old剩餘 → 觸發 FGC
}
YGC 完成後,若歷史平均晉升量超過 Old 區剩餘空間,立即觸發 Full GC。
| 循環 | Eden | Old | YGC | FGC | 說明 |
|---|---|---|---|---|---|
| 1 | 3 | 0 | 0 | 0 | |
| 2 | 6 | 0 | 0 | 0 | |
| 3 | 3 | 6 | 1 | 0 | 首次YGC,3MB晉升 |
| 4 | 6 | 6 | 1 | 0 | |
| 5 | 3 | 12 | 2 | 0 | |
| 6 | 6 | 12 | 2 | 0 | |
| 7 | 3 | 18 | 3 | 1 | YGC後Old剩2MB < 平均晉升6MB → 觸發FGC |
| 8 | 6 | 18 | 3 | 1 | |
| 9 | 3 | 3 | 3 | 2 | YGC前預檢:min(6,6)=6MB > Old剩餘 → 跳過YGC直觸發FGC |
悲觀策略假設程式穩態運行時每次 YGC 晉升量固定,提前檢查避免「YGC 後晉升導致 Old 區 OOM 再 Full GC」的雙重開銷。這也是為何有時 Old 區未滿卻觸發 Full GC 的原因。
思考題:將參數改為 -XX:+UseSerialGC 運行會如何?歡迎實驗驗證。