GC 日志里每八分钟出现一次 promotion failed,随后跟一次 Full GC,日志写着 to-space exhausted,接口在那一秒全部超时。第一次遇到会往复杂处想,其实漏掉的多是个小前提。

java -Xms8g -Xmx8g -Xmn3g -XX:SurvivorRatio=8 \
  -XX:+UseG1GC -Xlog:gc*:file=/tmp/gc.log:time -jar app.jar
grep -c 'promotion failed' /tmp/gc.log

年轻代只有 1G,Survivor 放不下一次 Minor GC 后存活的对象,晋升时老年代也没有连续空间,回收器只能退化成 Full GC。对象创建速率高时更明显。这一点先确认清楚,后面就不用反复试。

碰到「晋升失败」这类问题,我习惯先把当前配置原样备份一份,改坏了立刻换回来,不用现场推。把复现步骤压到三条以内,交给同事也能跑出来,才算真的定位清楚。

改动写完就同步给同事,下一个人踩同一个坑,成本还是你的。验证用真实数据走一遍,测试数据太干净,边界情况根本碰不到。

具体做法是,把年轻代调大 用 -Xmn 或 NewRatio 把 Eden 提到 3G 以上;让 Survivor 能容纳一批存活对象 避免直接晋升;查是否有大对象直接进老年代 调高 PretenureSizeThreshold;用 GC 日志确认 promotion failed 是否消失。

改完照着核一遍,压测一小时 gc.log 里 promotion failed 次数为 0 Full GC 不再被触发。

作者 9823189