预防 full gc 频繁发生需“参数+代码+监控”协同:固定堆大小、限制大对象直入老年代、禁用显式gc、增大元空间上限;优先选用g1/zgc/shenandoah;避免大对象创建、流式处理、对象复用、缓存限界;通过gc日志和heap dump定位真实瓶颈。

预防 Full GC 频繁发生,关键不是消灭它,而是控制它——让老年代不被快速填满、不被碎片化、不因晋升失败或元空间耗尽而被迫回收。真正有效的策略是“参数 + 代码 + 监控”三者协同,缺一不可。
调优 JVM 参数,守住内存分配底线
参数不是越多越好,而是要解决最痛的几个点:
- 固定堆大小:-Xms 和 -Xmx 设为相同值(如 -Xms4g -Xmx4g),避免运行中扩容触发 Full GC
- 限制大对象直入老年代:设 -XX:PretenureSizeThreshold=4194304(4MB),让中等大小对象仍走年轻代路径,便于 Minor GC 提前清理
- 禁用显式 GC:加 -XX:+DisableExplicitGC,防止框架或误写 System.gc() 强行拉起 Full GC
- 增大元空间上限:JDK8+ 加 -XX:MaxMetaspaceSize=512m,避免动态加载类过多导致元空间耗尽
选对垃圾收集器,降低停顿风险
Parallel Old 在 Web 场景下容易卡顿,应主动切换:
- JDK8 及以上优先用 G1:-XX:+UseG1GC -XX:MaxGCPauseMillis=200,它分区管理、可预测停顿
- JDK11+ 可评估 ZGC 或 Shenandoah:亚毫秒级停顿,适合低延迟敏感服务
- 明确弃用 CMS:已废弃,且易因碎片化反复触发 Full GC
从代码源头减少老年代压力
很多 Full GC 是“自己造出来的”,重点防三类行为:
- 拆大为小:导出百万数据不用 list.stream().collect() 生成大 List,改用 forEach + 分批 flush 到 OutputStream
- 流式替代全量:JSON 序列化不用 toJSONString(bigObj),改用 ObjectMapper.writeValue(response.getOutputStream(), obj)
- 复用代替新建:循环内不用 new StringBuilder(),改用 clear() 后复用;ByteBuffer.allocateDirect() 也建议池化复用
- 缓存加约束:static Map 缓存必须带大小限制和 LRU 清理逻辑,不能无界堆积 byte[] 或大对象
用工具定位真实瓶颈,别猜
日志和堆转储才是真相来源:
- 开 GC 日志:-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
- 用 GCViewer 或 GCEasy 分析:看老年代使用率是否阶梯上升、每次 Full GC 前是否有 Promotion Failure
- 抓 Heap Dump:jmap -dump:format=b,file=heap.hprof
,用 MAT 查 “largest objects” 和 “leak suspects” - 重点关注:静态集合是否持续增长、ThreadLocal 是否未 remove、连接/流是否未 close
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











