老年代回收速度跟不上分配速度的根本原因是mixed gc清理能力被压垮,需提前触发并发标记、收紧回收范围、减少无效晋升,并通过日志指标验证效果。

老年代回收速度跟不上分配速度,本质是 Mixed GC 的清理能力被“压垮”了——不是它不想清,而是标记没完成、回收集选得不准、或晋升太猛导致老年代持续膨胀。解决方向很明确:让并发标记更早启动、让每次 Mixed GC 更聚焦、从源头掐断无效晋升。
提前触发并发标记,别等堆快满了才动手
默认 IHOP=45% 太保守。当长期存活对象多时,老年代可能在堆总占用才 40% 时就已逼近极限,但 G1 压根不启动标记,自然没有后续 Mixed GC。应把 InitiatingHeapOccupancyPercent 调到 30%~35%,让标记周期提早介入。调完后盯日志里的 initiating occupancy 字段,确认真实触发点是否落在目标区间;低于 30% 易引发 Mixed GC 频繁,反而拖累吞吐。
收紧回收范围,只碰“值得清”的老年代 Region
Mixed GC 不是扫全量老年代,而是按“存活率低 + 回收收益高”原则挑 Region。默认设置太宽松,容易浪费时间扫描那些装满长期变量的“硬核 Region”:
- G1MixedGCLiveThresholdPercent=65:只回收存活对象 ≤65% 的 Region(默认 85%),跳过高存活率区域
- G1OldCSetRegionThresholdPercent=10:单次最多选老年代 Region 总数的 10%(默认无上限),防止单次压力过大
堵住晋升漏洞,减少老年代“被动扩容”
很多所谓“长期存活变量”,其实是本不该进老年代的:
- 缓存用 WeakReference/SoftReference 包装,并配合 ReferenceQueue 主动清理
- 批量加载改用 Stream.iterate().limit(n) 分页,避免万级 List 直接撑爆 Survivor 区而强制晋升
- 检查 ThreadLocal 使用场景,线程池中未调用 remove() 会导致 Value 对象长期滞留老年代
验证是否真有效,盯紧三个日志指标
不看 GC 日志等于没调:
- Mixed GC 耗时:是否稳定在 MaxGCPauseMillis 目标内(如 100ms),且波动小
- Old CSet 大小:日志中 old regions 数字是否下降,说明筛选更准、无效扫描减少
- Evacuation Failure:一旦出现,说明 Survivor 或老年代预留空间不足,需调大 G1ReservePercent(建议 10%~20%)










