Chrome内存快照里怎么判断是不是内存泄漏?
我在用 Chrome DevTools 的 Memory 面板拍快照,但不太会看那些 retained size 和 distance,到底怎么看才能确定是不是真的内存泄漏了?
我试过反复打开关闭一个组件,按理说应该被回收,但快照里还是能看到很多 detached DOM tree,像这样:
function createModal() {
const div = document.createElement('div');
div.innerHTML = '<button>Close</button>';
document.body.appendChild(div);
div.querySelector('button').onclick = () => {
document.body.removeChild(div);
};
}
这种写法是不是会导致闭包引用没释放?该怎么分析?
1. 首先,先创建一些对象,然后清理掉它们。比如你提到的
createModal函数,创建了一个模态框然后移除它。2. 拍下第一个快照,记录下当前内存状态。
3. 再次创建并清理对象,确保它们被正确移除。
4. 再拍下一个快照。
5. 对比这两个快照,看看是否有对象的数量没有减少。特别是那些 detached DOM trees,它们应该是被垃圾回收机制清理掉的。
对于你提到的代码,主要问题是闭包可能导致的引用。在这个例子中,事件处理函数会捕获外部的
div变量,这意味着即使div被移除,这个闭包依然会保持对它的引用。解决这个问题的一个方法是手动断开引用,比如在移除元素后将事件处理函数置为 null:这样做可以确保在移除 DOM 元素后,相关的事件处理程序也会被清理掉,减少内存泄漏的风险。记得多拍几次快照,对比对象数量的变化,这样才能更好地判断是否有内存泄漏。
retained size 是关键,这个值表示这个对象以及它引用的所有东西总共占多少内存。distance 是到GC根的距离,距离越小越可疑,说明它很可能是泄漏链的源头。
你说的 detached DOM tree 就是典型问题。你那段代码确实有闭包泄漏:
点击关闭按钮后,div 虽然从 DOM 移除了,但 onclick 回调函数形成了一个闭包,这个闭包还持有着 div 的引用。事件处理器没清掉,对象就回收不掉。
怎么分析:
1. 在 Memory 面板拍下初始快照
2. 重复操作你的功能(比如打开关闭弹窗)十几次
3. 再拍一次快照
4. 选择第二个快照,切换到 Comparison 视图
5. 看 # Delta 列,正数的就是增长的对象
找到泄漏对象后,点进去看 Retainers 面板,一层层往上追,看是什么引用链保持着它。
修复办法:
或者更彻底的做法是用 EventListener,并且确保在组件销毁时 removeEventListener。关键就是手动断开引用,让 GC 能回收。