← 中文资源

简体中文版本

内存泄漏排查工作流

先用趋势分析确认增长是泄漏,再用堆快照二分保留集,最后以浸泡负载下的平稳曲线验证修复。

阅读英文原文

目标

内存排查常败在拿苹果比橘子。在同一工作负载阶段、只隔着增长间隔取两个堆快照,恰好显示出哪个对象群体在膨胀;保留链再点名是谁在让它们活着。要修的是保留机制(那个从不解绑的 close 监听器),而不是受害的对象本身(把 Session 改小)。

步骤要点

  1. 先确认是泄漏不是缓存:稳态负载下画内存随时间的曲线——泄漏单调增长、缓存趋于平台、GC 压力往复振荡,形状决定打法。
  2. 在增长曲线上取两个同阶段快照:同一工作负载阶段相隔一段增长取堆快照,对比保留大小。
  3. 二分保留路径:沿增长增量的保留链上溯——哪个 Map、监听器注册表或闭包在对象失效后仍持住它们。
  4. 修复保留机制:解绑监听器、给 Map 加 LRU 上界、关闭资源或清理持有闭包的定时器。
  5. 用浸泡运行验证:修复前后各跑数小时持续负载,堆曲线保持平稳且进程活过预期运行时长。

排错提示

两个快shot差着工作负载阶段比出来的全是噪音,务必同阶段对比;修了对象没修机制泄漏照样活,按保留链修到持有人;快照平坦就收工为时尚早,慢泄漏或突发阶段只有整段时长的浸泡负载才能证伪。

适用边界

堆快照本身会暂停进程,生产环境要摘流量后抓取或用低开销采样替代;原生内存(Buffer、堆外)不在对象图里,需进程级 RSS 与工具配合分析。

代码示例

trend (steady 200 rps, 1h):  RSS 380 -> 742 MB  monotonic = LEAK

snapshot A (after 10 min)  vs  snapshot B (after 60 min)
  delta, by retained size:
    Session objects      +38,114  (4.1MB -> 212MB)
    retainer chain:
      Session -> sessionCache Map -> disconnect handler
      -> socket close listener never removed

fix: registry.unbind(socket) in close handler
  + LRU bound on sessionCache (10k)

snapshot C vs D (post-fix, same protocol):
  Session delta: +41  (noise)      FLAT

soak (intended uptime 72h): RSS 380 -> 391MB  PASS