年轻代“假死”是AdaptiveSizePolicy错误压缩Survivor空间致tenuring threshold强制为1,引发YGC频发、对象直升老年代;可通过GC日志、jstat、jmap定位,并以禁用自适应、固定年轻代大小、设定SurvivorRatio和MaxTenuringThreshold修复。

年轻代“假死”不是进程挂了,而是应用还在跑、接口却持续超时或无响应——典型表现是 YGC 频繁(比如每秒好几次),但每次回收后 Eden 几乎立刻又满,Survivor 区几乎不存对象,大量本该短期存活的对象直接冲进老年代,老年代快速膨胀,最终触发频繁 Full GC,系统卡住不动。
看 GC 日志确认 AdaptiveSizePolicy 是否在“乱调”
启动 JVM 时加上这些参数,让日志暴露自适应行为:
- -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy
- -Xlog:gc+age=debug(JDK 10+)或 -XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput(旧版)
重点搜这几类线索:
- 日志里反复出现 Desired survivor size 被重设,且数值跳变剧烈(如从 4MB → 1MB → 2MB)
- 出现 tenuring threshold forced to 1 或 AdaptiveSizePolicy::compute_eden_size 明确缩小 Survivor
- YGC 后晋升量突增(如从几百 KB 暴涨到几十 MB),同时 Survivor 使用率长期低于 5%
用 jstat 实时验证 Survivor 容量是否抖动失衡
执行:jstat -gc <pid> 1000,每秒刷新一次,盯住这三列:
- S0C / S1C:如果在 1–4MB 之间反复跳变(比如 8MB → 2MB → 1MB),说明 Survivor 被自适应策略反复压缩
- EC(Eden Capacity):常同步大幅膨胀(如 512MB → 900MB),这是它“补偿式扩容 Eden”来压低 YGC 频率,反而加剧问题
- YGC / YGCT:次数飙升(>10 次/秒)、单次耗时变长,但 YU(Young Used) 回收后仍紧贴 EC,说明存活对象根本没进 Survivor,全被“踢”去老年代
用 jmap 快照定位对象真实去向
在 YGC 高频发生时立即抓快照:
- jmap -histo:live <pid> | head -20:看 top 20 是否全是短期对象(byte[]、char[]、StringBuilder、DTO 类),且带明显业务包名
- jmap -heap <pid>:检查输出中 PSYoungGeneration 下的 FromSpace/ToSpace 容量是否远小于预期(比如你设了 -XX:SurvivorRatio=8,但实际 S0C 只有 1MB,而 EC 达 1GB)
- 再对比 PSOldGeneration.used:如果每次 YGC 后都猛涨(如 +30MB~+50MB/秒),而 OC 不变,就是对象批量提前晋升
快速止血与稳定修复
这不是微调,是夺回控制权:
- 加参 -XX:-UseAdaptiveSizePolicy 彻底禁用自适应策略
- 显式固定比例:-XX:SurvivorRatio=6(比默认 8 更宽松,留缓冲空间)
- 限制晋升年龄:-XX:MaxTenuringThreshold=6,避免因 Survivor 溢出被迫把对象“一脚踹进老年代”
- 上线后紧盯两个指标:单次 YGC 晋升量是否

















