必须关闭useadaptivesizepolicy,否则-xmn和-xx:survivorratio等参数会被jvm动态覆盖,导致eden扩大、survivor压缩至1mb级,引发tenuring threshold强制为1、对象过早晋升;关闭后配合-xmn、-xx:survivorratio、-xx:maxtenuringthreshold才能实现gc行为可预测、长尾可控。

直接用 -Xmn 固定年轻代总大小,再配合禁用自适应策略和 Survivor 比例锁定,是压测中稳定 GC 行为、压平 P95/P99 长尾最直接有效的方式。它不依赖 JVM 的运行时猜测,而是把 Eden、Survivor 的容量变成确定值,让 Minor GC 触发时机和对象晋升路径可预期。
为什么必须关掉 UseAdaptiveSizePolicy
默认开启的 -XX:+UseAdaptiveSizePolicy 会持续覆盖你设的参数:哪怕写了 -Xmn512m 和 -XX:SurvivorRatio=8,JVM 仍可能在几次 GC 后悄悄把 Eden 拉到 700MB、Survivor 压到 1MB,导致大量对象躲不过一次 GC 就晋升老年代。日志里频繁出现 “Desired survivor size” 被重设 或 “tenuring threshold forced to 1”,就是它在乱调的铁证。
- 加 -XX:-UseAdaptiveSizePolicy 彻底关闭,这是固化前提
- 关闭后,-Xmn、-XX:SurvivorRatio、-XX:MaxTenuringThreshold 才真正生效
- 别只关 AdaptiveSizePolicy 却漏掉其他浮动项——同步禁用 -XX:+UseAdaptiveGCBoundary(G1 下)和 -XX:+UseAdaptiveGenerationSizePolicyAtMajorCollection(Parallel GC 下)
怎么设一个真正稳得住的 -Xmn 值
不是拍脑袋定个“够大”的数,而是从压测基线反推:用 JFR 或 GC 日志采集稳定流量下连续 30 分钟的 Minor GC 行为,重点关注:
- Eden 从空到满的平均耗时(比如 1.8s)
- 每次 GC 后 Eden 清空率(应 ≥95%,若常低于 90%,说明对象存活率高或 GC 不及时)
- Survivor 区峰值占用(比如 S0 最高用到 42MB)
- 单次晋升到老年代的对象量(理想应
取 Eden 实测峰值占用 × 1.2~1.5 作为 -Xmn 值。例如实测 Eden 最高占 80MB,则设 -Xmn120m(注意:-Xmn = Eden + S0 + S1 总和,不是仅 Eden)。
配套必须锁死的两个关键比例
只固大小不够,Survivor 区太小或晋升年龄失控,一样会引发长尾:
- -XX:SurvivorRatio=6(推荐值,比默认 8 更安全):确保 Eden:S0:S1 = 6:1:1,给短期存活对象留出缓冲空间;若实测 Survivor 峰值长期 >85% 占用,可进一步调低至 4
- -XX:MaxTenuringThreshold=6(根据幸存对象年龄分布调整):避免对象在 Survivor 区反复复制却不晋升,也防止因 Survivor 溢出被强制“1岁就送走”
上线后怎么验证真稳了
不能只看 GC 次数下降,要盯住两个硬指标:
- Minor GC 间隔的标准差:压测稳态期应
- 单次 Minor GC 耗时的 P90:应稳定在 15ms 内,且波动幅度
- 若仍跳变,优先检查是否还有其他参数干扰:比如没关掉 -XX:+UseAdaptiveGCBoundary,或容器内存限制(cgroup v1/v2)导致 JVM 误判可用堆大小










