在 Chrome 开发工具中查找 JS 内存泄漏

Posted

技术标签:

【中文标题】在 Chrome 开发工具中查找 JS 内存泄漏【英文标题】:Finding JS memory leak in chrome dev tools 【发布时间】:2012-08-09 10:21:03 【问题描述】:

我正在使用 chrome 开发工具来确定某些 JS 代码中是否存在内存泄漏。 内存时间线看起来不错,内存按预期回收。

但是,内存快照令人困惑,因为它看起来像是存在泄漏,因为“分离的 DOM 树”下有条目。

“分离的 DOM 树”下的东西是等待垃圾回收还是这些真正的泄漏?

还有谁知道如何找出哪个函数在对分离元素的引用保持不变?

【问题讨论】:

我发现“Detached DOM Tree”里面的东西是jQuery为了提高性能而缓存的文档片段 【参考方案1】:

这些元素在您的代码中被引用,但它们与页面的主 DOM 树断开连接。

简单示例:

var a = document.createElement("div");

a 现在引用了一个断开连接的元素,当a 仍在作用域内时,它不能被 GC'd。

如果分离的 dom 树在内存中持续存在,那么您将保留对它们的引用。使用 jQuery 做到这一点有点容易, 只需保存对遍历结果的引用并保留它。例如:

var parents = $("span").parent("div");
$("span").remove();

现在跨度被引用,即使看起来你并没有引用它们。 parents 间接保留引用 通过 jQuery .prevObject 属性到所有跨度。因此,parents.prevObject 将提供引用所有跨度的对象。

在此处查看示例http://jsfiddle.net/C5xCR/6/。即使没有直接显示跨度会被引用, 它们实际上被 parents 全局变量引用,您可以看到 Detached DOM 树中的 1000 个跨度永远不会消失。

现在这是相同的 jsfiddle,但带有:

delete parents.prevObject

你可以看到 span 不再在分离的 dom 树中,或者任何地方。 http://jsfiddle.net/C5xCR/7/

【讨论】:

+1 用于提及全局变量,没有想到它们,但它们很可能是许多分离的 DOM 引用的原因 @EliasVanOotegem 它不一定必须是全局的,只是在闭包或对象属性或任何东西中持久保存的东西。我的主要观点是,只要最新遍历的 jQuery 对象在内存中,遍历的 jQuery 如何将所有旧引用保留在内存中并不是很明显。 api.jquery.com/category/traversing/tree-traversal 也考虑过提及这一点(因此我建议通过从 jQuery 对象中检索 DOM 引用来删除元素)。我没有提到像(function globalFunc (elem)return elem.value;)(document.getElementById('foo')); 这样的代码,因为我已经得到了很多关于过于牵强之类的代码,但我链接到一个关于闭包的问题和取而代之的是内存泄漏【参考方案2】:

“分离的 DOM 树”下的东西是只是在等待垃圾回收还是这些真正的泄漏?

在拍摄快照之前,浏览器将运行垃圾收集并清除所有未引用的对象。所以堆快照总是只包含活动对象。因此,如果快照中存在分离的 DOM 树,则树中必须有一个从 javascript 引用的元素。

还有谁知道如何找出是什么函数持有对分离元素的引用?

在同一个分离的 DOM 树中应该有一个(或多个)元素具有黄色背景。这些元素是从 JavaScript 代码中引用的。您可以找出谁在保留器树中准确地保留了对该元素的引用。

【讨论】:

您回答的第二部分非常有帮助。谢谢你。你知道红色背景是什么意思吗? 红色的是 DOM 节点,仅由 DOM 树保留,javascript 代码没有直接引用它们。这尤其意味着分离的 dom 树中的所有元素不可能都是红色的,因为在这种情况下会收集树。总是至少有一个节点具有黄色或白色背景。 @YurySemikhatsky 有没有一种简单的方法可以找到黄色背景的? @YurySemikhatsky 谢谢没有人解释红色的含义。既然你这么说,我的 memleak 就有了模式:)【参考方案3】:

由于您添加了 jQuery 标记,我偷偷怀疑这是一个 jQuery 事物。一个快速的谷歌将我带到this page。当使用 jQ 的 detach 方法时,对该对象的引用仍保存在内存中,因此可能会导致您的快照。

另一件事可能是 jQuery 手头有一个 div 节点,它 - 显然 - 保存在内存中,但从未添加到实际的 dom 中......一种document.createNode('div') 而不附加它。这也将显示在内存快照中。你无法解决这个问题,jQuery 使用它将字符串解析为 html 元素。

所以要从内存中删除一些元素,使用 jQuery .remove() 方法,你的内存将被立即清除cf Esailija 关于为什么 remove 不太合适的评论账单在这里$('#someElem')[0].parentNode.removeChild($('#someElem')[0]); 应该完全删除该元素,但可能不会取消绑定事件。大概是这样的:

$('#someElem').detach();//to remove any event listeners
$('#someElem')[0].parentNode.removeChild($('#someElem')[0]);//remove element all together

同样,正如 Esailija 在他的回答中指出的那样,确保将对任何 jQuery 对象 (var someRef= $('.someSelector');) 的引用分配给全局变量,因为它们不会被 GC 处理。实际上,只需避免使用全局变量即可。 但要简短而明确地回答您的问题:不,这些不是真正的内存泄漏,应该在onbeforeunload 事件上释放内存。 jQuery 对象被删除,因此所有引用都超出了范围。至少,这是我的“研究” 让我相信的。 Perhaps not entirely relevant, but just as a reference 这是我不久前发布的关于 mem-leaks 的问题,我发现了一些事情..

【讨论】:

jQuery .remove() 在这方面与 .detach() 没有任何不同。不同之处在于.remove() 会清除来自jQuery.cache 的数据(除非是通过用户代码,否则不会引用任何元素)。 谢谢,我已经有一段时间没有使用 jQuery 了。我的这部分答案也基于我链接的文章 请注意,那些 dom 移除方法 (.removeChild etc, jQuery's .remove, .detach etc) 只会断开元素与 dom 的连接。这意味着为它清除一些引用,因为它的 dom 树兄弟、父母等不再引用它,但这并不意味着所有引用都被清除。例如,文档中的任何元素都是全局元素,因为例如 window.document.body.firstChild.nextSibling.nextSibling.firstChild 是对它的直接引用。从主 dom 树中删除它只是删除这些引用,但不一定全部删除。 是的,但在这种情况下就足够了。我们都指出他应该确保所有引用变量都符合垃圾收集的条件,暗示了在这方面关闭的风险等等...... 不在主 DOM 树中的元素在此处给出,因为它显示在 Detached DOM tree 类别中。所以我的观点是,任何 dom 删除方法在这里都是无效的,因为它们一开始就没有附加。

以上是关于在 Chrome 开发工具中查找 JS 内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

JS内存泄漏 和Chrome 内存分析工具简介(摘)

使用 Selenium 和 Chrome 开发工具的浏览器内存泄漏自动化

检测代码中的 node.js/javascript 内存泄漏

Android内存泄漏查找和解决

在没有 3rd 方工具/项目的 MFC C++ 版本中查找内存泄漏

如何使用 visualvm 查找内存泄漏