定位内存泄漏需结合gc日志、线程快照与堆转储:先通过gc日志识别full gc频次上升、老年代回收无效及异常提示;再用jstack分析waiting/blocked线程,排查threadlocal等未清理资源;最后用jmap+mat分析堆转储,追溯gc roots定位泄漏对象;修复后需监控验证效果。
直接看 gc 日志和线程状态,再结合堆转储分析,就能定位内存泄漏引发的高频、高耗时 gc 停顿。关键不是调参数,而是确认“对象为什么没被回收”。
观察 GC 日志确认异常模式
先确认是不是真有泄漏,而不是堆太小或流量突增。重点看三件事:
- Full GC 是否越来越频繁(比如从每小时 1 次变成每分钟 2–3 次)
- 每次 Full GC 后老年代使用率是否没明显下降(例如停在 95% 以上反复横跳)
- GC 日志中是否有 “promotion failed”、“concurrent mode failure” 或 “Metaspace OOM” 等提示
启动时加上这些参数开启详细日志:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/app/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=10M抓取线程快照排查阻塞与资源滞留
内存泄漏常伴随线程行为异常:比如大量线程卡在 WAITING 或 BLOCKED,说明它们持有对象却没释放。
- 用 jstack
> thread_dump.log 获取当前所有线程状态 - 重点关注 WAITING (on object monitor) 和 BLOCKED 状态的线程数量是否持续增长
- 搜索关键词如 ThreadLocal、Cache、Listener、register,看是否有注册未注销、设置未 remove 的痕迹
例如 ThreadLocal 使用后没调 remove(),在线程池复用场景下会把上下文对象长期挂在 GC Roots 上。
一款AI音频处理工具,主要用于通过向 `toingg/make_campaign` API 发送 POST 请求,提交用户提供的 JSON 数据,创建并启动 Toingg 语音呼叫活动,适用于 Codex 将活动简报……,适合需要提升相关任务效率的用户。
生成并分析堆转储文件
光看日志和线程不够,得看到底是哪些对象占着不走。
- 手动触发 dump:jmap -dump:format=b,file=heap.hprof
(建议在 Full GC 后立刻执行) - 用 Eclipse MAT 打开,先看 Leak Suspects Report,它会自动标出最可疑的几组对象
- 再看 Dominator Tree,找占用内存 Top 3 的类,点进去看其 Retained Heap 和引用链
- 顺着 GC Roots 追溯:重点检查 static 字段、ThreadLocalMap、ClassLoader、活跃线程栈帧中的局部变量
常见泄漏源头包括:静态 HashMap 无限 put、监听器注册后忘记 unregister、数据库连接/流未 close、缓存没设过期或容量上限。
验证修复效果
改完代码后不能只看一次 GC 日志就认为好了。
- 重启应用,用 VisualVM 或 Prometheus 监控堆内存曲线,确认老年代使用率不再阶梯式上涨
- 压测相同流量,对比 Full GC 频率和单次停顿时长是否回落到基线水平(如从 6 秒/次降到 200ms/次)
- 定期(比如每 15 分钟)自动采集 jstack,检查异常线程数是否稳定










