老年代回收慢本质是对象太多、太散或根本回收不掉;若fullgc后老年代使用率几乎不变且频率持续上升,大概率是内存泄漏,需用jstat、jmap和堆转储分析确认根因。

老年代回收慢,本质是对象太多、太散、或根本回收不掉。关键得先判断是“回收耗时长”,还是“回收无效”——后者往往才是真问题。
先确认是不是内存泄漏
如果 FullGC 后老年代使用率几乎不变(比如只降 2%~3%),而频率持续上升,大概率是内存泄漏。这时回收再快也没用,因为对象还被 GC Root 强引用着。
- 用
jstat -gc <pid></pid>查看每次 FullGC 前后老年代(OU)和堆(OU+MU)的变化量 - 用
jmap -histo:live <pid></pid>对比多次输出,看哪些类实例数/占用持续增长 - 导出堆转储:
jmap -dump:format=b,file=heap.hprof <pid></pid>,用 VisualVM 或 Eclipse MAT 分析 dominator tree 和 leak suspect
选对垃圾回收器并合理配置
老年代回收慢,和回收器算法强相关。CMS 已废弃,G1 是当前主流,ZGC/Shenandoah 适合超低延迟场景。
- G1:适合堆大于 4GB 的服务,启用并设停顿目标:
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 - ZGC:JDK11+ 支持,停顿稳定在 10ms 内,需开启:
-XX:+UseZGC(注意系统需支持大页) - 避免 CMS:标记-清除不整理,碎片累积后会频繁触发 FullGC,且 JDK14+ 已移除
减少老年代压力和碎片
对象不该进老年代的,就别让它进去;进了的,也尽量别让它扎堆占坑。
- 调大新生代比例(如
-Xmn1g或-XX:NewRatio=2),减少因 Survivor 溢出导致的过早晋升 - 增大 Survivor 空间(
-XX:SurvivorRatio=6),让对象多熬几轮 MinorGC,延长筛选时间 - 降低晋升年龄阈值(
-XX:MaxTenuringThreshold=4),避免中年对象反复复制,也防止“卡在 Survivor 不走” - 避免长期持有大对象(如缓存未清理的 byte[]、未关闭的 DirectByteBuffer),它们直接进老年代且难回收
检查系统与 JVM 资源约束
回收慢有时不是 JVM 的问题,而是它没资源可使。
- 观察
top中 Java 进程 RES 是否远超-Xmx(比如 -Xmx2g 但 RES 2.7g),说明存在堆外内存泄漏(如 Netty 的 PooledByteBufAllocator 或 JNI) - Swap 使用率高(>80%)会导致 GC 线程读写缓慢,应关闭 Swap 或确保物理内存充足
- CPU 持续满载(>150%)会让 GC 线程抢不到执行权,尤其影响并发回收器(如 G1 的并发标记阶段)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











