gc日志是内存行为的实时快照,需聚焦高频、异常、收尾三类信号,结合年轻代/老年代变化趋势定位问题,辅以堆镜像分析引用链,优先修复代码泄漏而非盲目调参。

GC 日志不是待解读的“谜题”,而是代码内存行为的实时快照。它不直接指出哪行代码有错,但会清晰暴露对象怎么分配、活多久、为何晋升、是否滞留——这些正是排错和重构的起点。
盯住三类关键信号
不必通读全部日志,先聚焦高频、异常、收尾三类信息:
-
高频信号:一秒内多次出现
[GC (Allocation Failure)],说明 Eden 区持续填满又清空,大概率是循环中反复创建短命对象(如字符串拼接、流式 collect) -
异常信号:Full GC 后老年代
used只降 1–2%,或耗时逐次拉长(如从 300ms → 800ms → 1.5s),提示对象无法回收,存在强引用滞留 -
收尾信号:日志末尾出现
java.lang.OutOfMemoryError: Java heap space或GC overhead limit exceeded,立刻往上翻 5–10 行,定位最后一次 Full GC 前后的内存快照
结合三组数字看趋势
每条 GC 记录里都含类似 [PSYoungGen: 8192K->1024K(9216K)] [ParOldGen: 5120K->9216K(9216K)] 的结构,重点比对连续多行中的变化:
- 年轻代长期 >95% + Minor GC 频繁但回收量小 → Survivor 区太小,或对象存活率高(如大数组、缓存未清理)
- 老年代缓慢但持续上升,每次 Full GC 后仅微降 → 典型泄漏迹象,常见于静态 Map、未注销监听器、ThreadLocal 未 remove
- Metaspace 使用率持续上涨并触发 Full GC → 动态类加载过多(如热部署、Groovy 脚本、大量代理类)
用堆镜像验证“谁没被回收”
日志只能告诉你“哪里降不下去”,堆转储才能回答“为什么降不下去”:
- 主动抓取:GC 频繁时执行
jmap -dump:format=b,file=heap.hprof <pid></pid>,避免等 OOM 才行动 - 打开后按 Retained Heap 排序,找真正拖住内存的对象类型(不是实例数最多,而是被谁长期持有)
- 点开可疑类的 Reference Chain,重点检查顶层是否为 static、ThreadLocal、ClassLoader 或未关闭的资源句柄
调参前先确认是不是代码问题
盲目加大堆或调高 SurvivorRatio 往往掩盖真因。优先检查三类高频源头:
- 静态容器:static Map/Cache 没设上限、没配驱逐策略 → 改用 Caffeine 或加定时清理
- 内部引用泄露:非静态内部类被线程池长期持有 → 改为静态内部类 + 显式传参
- 资源未释放:InputStream / Socket / Connection 未 close → 用 try-with-resources 或 finally 强制释放
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











