【问题标题】:Locating detached DOM tree memory leak定位分离的 DOM 树内存泄漏
【发布时间】:2014-02-09 04:31:42
【问题描述】:

我在诊断一个主要使用 Knockout 构建的非常大的单页 Web 应用程序中的分离 DOM 树内存泄漏时遇到了问题。

我对应用程序进行了调整,以将虚拟 FooBar 对象附加到特定的 HTML 按钮元素,当用户移动到应用程序的不同“页面”时,该元素应该被垃圾收集。使用 Chrome 的 堆快照 功能,我可以看到旧的 FooBar 实例(应该已经被 GC'ed)仍然可以从它的 HTMLButtonElement 在(大)分离的 DOM 树中访问。

通过retaining tree面板跟踪引用,我跟随从 GC 根的距离递减的链。然而,在某个时候,我的搜索在距离根节点 4 处到达死胡同(在这种情况下)!保留树报告根本没有对该节点的引用,但不知何故知道它距离 GC 根有四个步骤。

这是让我困惑的挡土树部分(右边的数字是到根的距离):

v foobar in HTMLButtonElement                                  10
  v [4928] in Detached DOM tree / 5643 entries                  9
    v native in HTMLOptionElement                               8
      v [0] in Array                                            7
        v mappedNodes                                           6
          v [870] in Array                                      5
            v itemsToProcess in system / Context                4
                context in function itemMovedOrRetained()
                context in function callCallback()

保留树不会在此处显示距离为 3 或以上的引用。

谁能给我解释一下?我希望我能够追踪引用链回到 JavaScript 应用程序代码的违规部分——但这阻碍了我!

【问题讨论】:

  • 你能分享一些代码,特别是这个元素的创建位置和处理位置吗?
  • 恐怕代码是专有的且庞大的;我还没有机会尝试在小范围内复制它。这里真正的问题是为什么 Chrome 堆分析器应该报告类似上面的东西,它不会在根部触底(这怎么可能?!)。
  • 在删除 DOM 元素之前,您必须删除属性 foobar( 和 delete; delete button.foobar 并确保没有剩余的事件侦听器。
  • 我不认为这是一个错误。目前,我正在使用我自己的框架和 DOJO 开发一个巨大的 SPA。内存泄漏也是一个大问题。由于我没有适当地破坏连接(事件侦听器)并且我正在引用其他对象(外部范围),因此创建的内存泄漏最多。将我的“正常”对象更改为模块模式后,我的泄漏消失了。总结;您能否发布代码如何使用foobar 属性创建按钮?
  • @Rafe 但是我们应该如何帮助你呢?解决方案已经给出:您仍然拥有对该对象的引用。 GC 正在计算对一个对象的剩余引用。如果它变为 0,则该对象将被垃圾收集。否则,当有可能再次调用一个对象时,总会有一个引用。内存泄漏有很多种:闭包、循环引用、.. 如果没有提供代码,我们只能向您解释理论。早些时候,我试图解决一个巨大的应用程序(超过 1GB JS)中的内存泄漏,

标签: javascript html google-chrome memory-leaks


【解决方案1】:

首先 - 不要使用 delete 作为建议的 cmets 之一。设置对null 的引用是处理事物的正确方法。 delete 打破了“隐藏班级”。要自己查看,请从 https://github.com/naugtur/js-memory-demo 运行我的示例

Rafe,您在 profiler 中看到的内容通常很难理解。您在此处发布的内容看起来确实很奇怪,可能是您的应用程序外部的错误或内存泄漏(浏览器也泄漏),但如果不运行您的应用程序,就很难判断。您的保留树以函数的上下文结束,并且可以通过对该函数的引用或共享该上下文的某些其他函数来保留。探查器可能太复杂而无法正确可视化它。

不过我可以帮你查明问题。

首先,转到 devtools 中的 Timeline 选项卡并使用它来观察泄漏发生的时刻。仅选择内存分配并开始录制。经历一个你期望泄漏的场景。保持蓝色的条形是泄漏。您可以在时间线中选择他们的周围环境并专注于他们的保留树。分离的 dom 树中最有趣的元素是红色元素——它们是从外部引用的。其余部分被保留,因为树中的任何元素都被引用,它引用了其他所有元素 (x.parentNode)

如果您需要更多详细信息,您可以在分析器中拍摄多个快照,以便在泄漏原因之前和之后获得一个快照(您通过时间线找到的 - 您现在知道导致它的确切操作) .然后,您可以在分析器中比较它们 - 有一个“比较”视图。这比其他人更容易理解。

您还可以保存分析器中的堆快照并将其发布到网上,以便我们查看。左侧列表中的每一个都有一个保存链接。


分析内存很难,实际上需要一些练习和对工具的理解。 你可以练习我演讲中的一些例子:

http://naugtur.pl/pres/mem.html#/5/2

但是使用内存分析器的真正完整指南是这个文档:

https://developer.chrome.com/devtools/docs/javascript-memory-profiling#looking_up_color_coding

更新链接: https://developers.google.com/web/tools/profile-performance/memory-problems/memory-diagnosis

【讨论】:

  • 非常感谢!我们现在很忙,但我会在下午有空的时候尝试一下你的建议。很高兴看到有人同意我的观点,即痕迹看起来很奇怪。
  • 以黄色突出显示的节点从 JavaScript 代码中直接引用它们。红色突出显示的节点没有直接引用。它们之所以活着,是因为它们是黄色节点树的一部分。通常,您希望关注黄色节点。修复您的代码,使黄色节点的存活时间不会超过它需要的时间,并且您还可以删除属于黄色节点树的一部分的红色节点。
  • 但是如果你只有红色节点呢?
  • @Legends 在这里。我也有一些节点计数问题,只有红色突出显示的分离节点。
猜你喜欢
  • 2013-05-29
  • 2014-02-04
  • 2017-11-16
  • 1970-01-01
  • 2020-05-28
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多