这是 Javascript 内存泄漏吗?

Is this a Javascript memory leak?

我已经在 Chrome 开发人员工具中分析了我的 Web 应用程序,并得出了如上所示的时间表。我正在创建和删除悬停功能的元素。我在 this article.

中读到过这个

这种模式是否表明我有内存泄漏?节点数(绿线)持续上升,在内部 GC 上没有下降。但是在我的强制 GC 上下降到零。这是常见行为吗?

我的意思是,它在内存中保存了很多节点,即使它们不存在。如果我检查 heap,不存在对 DOM-nodes 的引用(没有分离的-DOM 等),这让我认为这不是内存泄漏?

你的五美分是多少?

代码如下:

$(document).on("mouseenter", ".btn", function(e){
    var el = document.createElement("div");
    el.id = "box";
    document.body.appendChild(el);
});
$(document).on("mouseleave", ".btn", function(e){
    $("#box").remove();
});

对此不是 100% 确定,但我认为问题可能是您的 mouseenter/mouseleave 事件没有解除绑定?

好吧,让我们试着面对问题吧。

可能性:

Javascript内存管理如何工作?一切都取决于可达性:

  1. A distinguished set of objects are assumed to be reachable: these are known as the roots. Typically, these include all the objects referenced from anywhere in the call stack (that is, all local variables and parameters in the functions currently being invoked), and any global variables.
  2. Objects are kept in memory while they are accessible from roots through a reference or a chain of references.

http://javascript.info/tutorial/memory-leaks

所以基本上,当对象不可访问时,JS 会从内存中删除对象。让我们举个例子:

HTML:

<html>
<div class="ourDiv"></div>
</html>

JS:

$(".ourDiv").append(document.createElement("span"))
            .remove();

在这个例子中我有意使用 class 而不是 ID,你会明白为什么。我们的记忆看起来像这样:

  1. 内存中有 html、正文和 div 标签
  2. 我们将 span 附加到 ourDiv,因此我们在内存中有 html、正文、div 和 span 标签
  3. 删除 ourDiv 后,span 元素无法访问,因此在内存中我们有:html 和 body 标签

让我们将其修改为更类似于您的代码:

JS:

var newEl=document.createElement("span");
newEl.id = "ourSpan";
$(".ourDiv").append(newEl)
            .remove();

现在效果如何?

The element #id is an exception. It is accessible as #ourSpan, so it stays in memory. Of course if you check it’s parentNode, it would be null.

  1. 内存中有 html、正文和 div 标签
  2. 我们将带有 ID 的 span 附加到 ourDiv,因此我们在内存中有 html、正文、div 和 span 标签
  3. 删除 ourDiv 后,span 元素仍然可以访问,因为它设置了 ID,所以在内存中我们有:html、body 和 span

结论:

当您为每个创建的元素设置 ID 时,即使您尝试使用 jQuery 调用删除它们,javascript 也会将其保存在内存中。可能是因为将创建元素与原生 API 混合并在 jQuery 中将其删除。尝试检查您的 $.cache 大小 - 如果它太大,则意味着 jQuery 没有正确删除对象。

但首先 - 避免为您不断创建的元素使用 ID 应该会有所帮助。

更多解释见:Chrome Javascript docs, Firefox Javascript Docs

我在 Chrome 中显示的跟踪中没有看到内存泄漏。您缺少的信息是 当垃圾收集周期开始时,该周期可能 部分 收集垃圾。让我解释一下。

我不熟悉 v8 中的垃圾收集器,但在过去的不同时间我都在研究垃圾收集器。垃圾收集是快速释放未使用内存和应用程序响应之间的平衡行为。特别是在交互式应用程序中,您不希望长时间暂停执行以允许完整的垃圾收集周期,因为这会影响用户体验。因此存在垃圾回收周期可以 部分 而不是 完整 的策略。好像v8使用了这样的策略,因为Google's blurb on v8's garbage collection states:

This means that V8:

  • stops program execution when performing a garbage collection cycle.
  • processes only part of the object heap in most garbage collection cycles. This minimizes the impact of stopping the application.

因此,您不应期望大多数循环会将节点计数降为零。

为什么强制 GC 会将计数降为零?通过 Google 关于调试内存泄漏的文档的推断,我推断强制 GC 循环不仅强制开始 GC 循环而且 强制循环完成而不是部分, 否则强制循环对于想知道是否存在内存泄漏的人来说是无用的。