核心是识别跨多次gc仍不被回收的老年代长生命周期对象,需结合gc日志中老年代使用量阶梯式增长、晋升失败、高龄对象堆积等信号,关联堆转储分析retained heap和静态引用链定位泄漏根因。

通过 GC 日志排查长生命周期对象,核心是识别那些**跨多次 GC 仍不被回收的对象**,尤其是老年代中持续增长、长期驻留的实例。GC 日志本身不直接显示对象类型或引用链,但能暴露内存行为异常——比如老年代占用持续上升、Full GC 频繁但回收量少、晋升失败(Promotion Failed)等信号。关键在于结合日志指标 + 堆转储(heap dump)交叉验证。
看老年代使用趋势:判断是否存在对象堆积
启用详细 GC 日志(如 -Xlog:gc*,gc+age=debug:file=gc.log:time,tags,level 或旧版 -XX:+PrintGCDetails -Xloggc:gc.log),重点关注每次 Full GC(或 CMS/old GC)前后老年代(Old / Tenured)的使用量:
- 如果老年代使用量在多次 Full GC 后**不降反升**,或仅小幅下降(比如只回收几 MB),说明有大量对象长期存活
- 观察
Old space或tenured区的used值是否呈现阶梯式增长(每次 GC 后都比上次高),这是长生命周期对象缓慢积累的典型特征 - 对比
capacity和used,若used接近capacity且无法释放,可能已触发内存泄漏
抓取晋升异常和年龄分布:定位“本该死却活下来”的对象
年轻代对象晋升到老年代前会记录年龄(Age)。GC 日志中的 age 信息(需开启 -XX:+PrintTenuringDistribution 或使用 gc+age=debug)能揭示对象“赖着不走”的迹象:
- 关注
Desired survivor size和各年龄区间对象大小(如- age 1: 123456 bytes) - 如果某次 Minor GC 后,
age 15(最大阈值)的对象量持续增大,说明大量对象熬过了 15 次 GC —— 这远超正常业务对象生命周期(如缓存、连接池、监听器等易出现) - 出现
Promotion Failed或Concurrent Mode Failure,往往是因为老年代空间不足,而填充它的正是这些不该长期存活的对象
关联时间点触发堆转储:把日志线索落地为具体对象
GC 日志只是“报警器”,真正定位对象得靠堆快照:
- 在 GC 日志中发现异常时间点(如某次 Full GC 后老年代突增 200MB),立即用
jmap -dump:format=b,file=heap.hprof <pid></pid>抓取堆转储 - 用 Eclipse MAT、VisualVM 或 JProfiler 打开
heap.hprof,按 Retained Heap 降序排列,重点关注:- 实例数多、Retained Heap 大的类(如
HashMap、ArrayList、自定义缓存容器) - 持有大量对象引用的静态字段(
static引用是常见泄漏根因) - 未关闭的资源类(
ThreadLocal、数据库连接、文件句柄、监听器注册表)
- 实例数多、Retained Heap 大的类(如
- MAT 的 Leak Suspects Report 和 dominator tree 能快速定位强引用链起点
辅助验证:结合 jstat 实时观察对象年龄变化
运行时用 jstat -gc -t <pid> 5s</pid> 持续监控,观察以下字段动态变化:
-
S0U/S1U:Survivor 区使用量 —— 若长期非零且波动小,说明对象在 Survivor 中“卡住” -
EC/EU:Eden 区使用量周期性涨落正常;若EU居高不下且 Minor GC 频繁但回收少,可能是对象创建过快或 Survivor 空间不足导致提前晋升 -
OU(Old Used)持续单向增长 +OC(Old Capacity)不变 → 明确指向老年代对象滞留
不复杂但容易忽略:GC 日志只是入口,真正解决问题要日志 + 堆转储 + 代码逻辑三者对齐。重点不是“哪个对象大”,而是“谁一直持有着它”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











