最可靠的方式是用 chrome devtools memory 面板拍三张对比快照:基线快照(刷新+gc后)、操作一次后快照、重复操作2–3次后快照,通过 comparison 视图分析 Δ>0 的构造函数、retained size 异常增长及 detached 对象,并结合 retainers 和 dominators 定位未释放的强引用。

直接用 Chrome DevTools 的 Memory 面板拍多张快照并对比差异,是发现 JavaScript 内存泄漏最可靠的方式。关键不是看单张快照里谁最大,而是观察重复操作后哪些对象“只增不减”,再顺引用链找到本该断却没断的强引用。
拍三张有意义的快照
别一上来就点“Take Snapshot”。真正有用的对比,必须配合可控操作和垃圾回收:
- 先刷新页面,等加载完成、无交互,手动点击 Collect garbage(小垃圾桶图标),再拍第一张——这是基线快照(snapshot-0)
- 执行一次疑似泄漏的操作(比如打开又关闭模态框、切换一次路由、渲染一批列表),等 2–3 秒,再点一次 Collect garbage,拍第二张(snapshot-1)
- 重复同样操作 2–3 次,再 GC,拍第三张(snapshot-2)。至少三轮,才能看出增长趋势
在 Comparison 视图里盯住三类信号
选中 snapshot-2 → 右键 → Compare to previous snapshot,切换到 Comparison 列表,重点关注:
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
- Constructor 列中 Δ > 0 且持续上升的类型:比如 Closure、Array、UserInfo、ChartInstance,或 Detached HTMLDivElement —— 这些不是“多了”,而是“没被收走”
- Retained Size 明显增长,但 Shallow Size 几乎不变:说明这个对象本身不大,但它拖着一大片内存没释放(典型如闭包捕获了整个组件实例)
- Detached 对象反复出现且 Δ 稳定为正:不只是浏览器 DOM,Node.js 环境里 Buffer、EventEmitter、pendingRequests 也会被标为 Detached,它们往往就是泄漏入口
点进去看 Retainers,找不该存在的引用
双击某条高 Δ 的构造函数(如 Closure),右侧打开 Retainers 标签页,从上往下读引用链:
- 如果看到 window、document、全局 Map 或定时器回调 持有它,而对应逻辑早已结束,大概率就是泄漏点
- 常见模式:addEventListener 绑在 document 上却没 remove;setInterval 回调里闭包捕获了大数组且没 clearInterval;模块顶层缓存 Map 的 key 是 request 对象,没设 TTL
- 不要只看 Distance 值,重点看路径中是否存在本该销毁却仍活跃的上下文(比如已卸载的 React 组件、Vue 实例、webview 实例)
必要时切 Dominators 查支配源头
如果 Retainers 链太深、干扰多,可切换到 Dominators 标签页:
- 顶部节点往往是内存占用的起点,比如一个长期存活的 event listener 绑在全局对象上,导致整棵子树无法回收
- 结合顶部几个大节点的 Constructor 名和 Retained Size,快速锁定可疑模块或初始化代码段
- 对 Node.js 场景尤其有用:常能定位到 server.on() 多次绑定、未调用 removeAllListeners(),或插件资源未 dispose()
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










