使用Chrome查找JavaScript内存泄漏


163

我创建了一个非常简单的测试用例,该用例创建了Backbone视图,将处理程序附加到事件,并实例化了用户定义的类。我相信,通过单击此示例中的“删除”按钮,将清除所有内容,并且不会出现内存泄漏。

该代码的jsfiddle在这里:http : //jsfiddle.net/4QhR2/

// scope everything to a function
function main() {

    function MyWrapper() {
        this.element = null;
    }
    MyWrapper.prototype.set = function(elem) {
        this.element = elem;
    }
    MyWrapper.prototype.get = function() {
        return this.element;
    }

    var MyView = Backbone.View.extend({
        tagName : "div",
        id : "view",
        events : {
            "click #button" : "onButton",
        },    
        initialize : function(options) {        
            // done for demo purposes only, should be using templates
            this.html_text = "<input type='text' id='textbox' /><button id='button'>Remove</button>";        
            this.listenTo(this,"all",function(){console.log("Event: "+arguments[0]);});
        },
        render : function() {        
            this.$el.html(this.html_text);

            this.wrapper = new MyWrapper();
            this.wrapper.set(this.$("#textbox"));
            this.wrapper.get().val("placeholder");

            return this;
        },
        onButton : function() {
            // assume this gets .remove() called on subviews (if they existed)
            this.trigger("cleanup");
            this.remove();
        }
    });

    var view = new MyView();
    $("#content").append(view.render().el);
}

main();

但是,我不清楚如何使用Google Chrome的探查器来验证是否确实如此。堆探查器快照上显示了无数个庞大的信息,而且我不知道如何解码优缺点。到目前为止,我所看过的教程只是告诉我“使用快照事件探查器”,或者就整个事件探查器的工作原理给了我非常详尽的宣言。可以仅将探查器用作工具,还是真的必须了解整个项目是如何设计的?

编辑:像这样的教程:

Gmail内存泄漏修复

使用DevTools

从我所见,可以代表一些更强的材料。但是,除了介绍3快照技术的概念外,我发现它们在实践知识方面提供的很少(对于像我这样的初学者)。“使用DevTools”教程无法通过一个真实的示例进行工作,因此它对事物的含糊且笼统的概念描述并不太有用。至于“ Gmail”示例:

所以您发现了一个泄漏。怎么办?

  • 检查“轮廓”面板下半部分中泄漏物体的固定路径

  • 如果无法轻易推断出分配站点(即事件侦听器):

  • 通过JS控制台检测保留对象的构造函数,以保存堆栈跟踪以进行分配

  • 使用闭包?启用适当的现有标志(即goog.events.Listener.ENABLE_MONITORING)以在构造期间设置creationStack属性

阅读后,我发现自己更加困惑,而不是更少。再说一遍,这只是告诉我去做事情,而不是如何去做。从我的角度来看,那里的所有信息要么太含糊,要么只对已经了解该过程的人有意义。

以下@Jonathan Naguin的答案提出了其中一些更具体的问题。


2
对于测试浏览器中的内存使用情况,我一无所知,但是如果您还没有看到它,那么Addy Osmani的有关Chrome Web检查器的文章可能会有所帮助。
Paul D. Waite,

1
保罗,谢谢你的建议。但是,当我在单击“删除”之前拍摄一个快照,然后在单击“快照”之后拍摄另一个快照,然后选择“在快照1和2之间分配的对象”(如他的文章中所建议)时,仍然存在2000个以上的对象。例如,有4个“ HTMLButtonElement”条目,这对我来说毫无意义。确实,我不知道发生了什么。
EleventyOne

3
嗯,听起来并不是很有帮助。可能是因为使用了JavaScript之类的垃圾收集语言,您实际上并没有打算在与测试相同的级别上验证您对内存的处理方式。检查内存泄漏的更好方法可能是调用main10,000次而不是一次,并查看最后是否使用了更多的内存。
Paul D. Waite

3
@ PaulD.Waite是的,也许。但是在我看来,我仍然需要进行细粒度分析来确定问题的确切原因,而不是仅仅能够说(或不说):“好吧,这里有一个内存问题”。而且,我确实得到了这样的印象:我应该能够在如此细粒度的级别上使用他们的探查器...我只是不确定如何:(
EleventyOne 2013年

Answers:


205

查找内存泄漏的一个很好的工作流程是三种快照技术,Loreena Lee和Gmail团队首先使用它来解决他们的一些内存问题。这些步骤通常是:

  • 拍摄堆快照。
  • 做东西。
  • 拍摄另一个堆快照。
  • 重复同样的东西。
  • 拍摄另一个堆快照。
  • 在快照3的“摘要”视图中筛选在快照1和2之间分配的对象。

在您的示例中,我修改了代码以显示此过程(您可以在此处找到),以延迟“骨干视图”的创建直到“开始”按钮的click事件。现在:

  • 运行HTML(使用此地址本地保存)并拍摄快照。
  • 单击开始以创建视图。
  • 拍摄另一个快照。
  • 单击删除。
  • 拍摄另一个快照。
  • 在快照3的“摘要”视图中筛选在快照1和2之间分配的对象。

现在您已经准备好查找内存泄漏!

您会注意到一些不同颜色的节点。红色节点没有从Javascript到它们的直接引用,但由于它们是分离的DOM树的一部分,因此它们仍然有效。从Javascript引用的树中可能有一个节点(可能是一个闭包或变量),但同时防止了整个DOM树被垃圾回收。

在此处输入图片说明

但是,黄色节点确实具有Javascript的直接引用。在同一分离的DOM树中查找黄色节点,以查找Javascript中的引用。从DOM窗口到元素应该有一系列的属性。

在您的个人中,您可以看到标记为红色的HTML Div元素。如果展开该元素,您将看到“缓存”功能所引用的元素。

在此处输入图片说明

选择该行,然后在控制台中键入$ 0,您将看到实际的功能和位置:

>$0
function cache( key, value ) {
        // Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
        if ( keys.push( key += " " ) > Expr.cacheLength ) {
            // Only keep the most recent entries
            delete cache[ keys.shift() ];
        }
        return (cache[ key ] = value);
    }                                                     jquery-2.0.2.js:1166

这是您的元素被引用的地方。不幸的是,您无能为力,这是jQuery的内部机制。但是,仅出于测试目的,将功能更改为以下方法:

function cache( key, value ) {
    return value;
}

现在,如果您重复此过程,您将看不到任何红色节点:)

说明文件:


8
感谢您的努力。实际上,教程中经常提到三种快照技术。不幸的是,细节经常被遗漏。例如,我很欣赏$0控制台中引入的功能,这对我来说是新的-当然,我不知道它在做什么或您如何使用它($1似乎没用,而$2似乎做同样的事情)。其次,您怎么知道突出显示行#button in function cache()而不是其他几十行?最后,还有一些红色的节点NodeListHTMLInputElement过,但我不出来。
EleventyOne

7
您怎么知道该cache行包含信息,而其他行则没有?有许多分支的距离比那个分支的距离短cache。而且我不知道你HTMLInputElement是怎么知道的是的孩子HTMLDivElement。我看到它在其中引用了(“ HTMLDivElement中的本机”),但它也引用了自己和两个HTMLButtonElement,这对我来说没有意义。我当然感谢您确定此示例的答案,但是我真的不知道如何将其归纳为其他问题。
EleventyOne

2
真奇怪,我使用的是您的示例,但结果却与您截然不同(从屏幕截图中)。不过,非常感谢您的协助。我想我现在已经足够了,当我有一个需要具体帮助的真实示例时,我将在此处创建一个新问题。再次感谢。
EleventyOne 2013年

2
可以在以下位置找到对$ 0的说明: developer.chrome.com/devtools/docs/commandline-api#0-4
Sukrit Gupta

4
什么Filter objects allocated between Snapshots 1 and 2 in Snapshot 3's "Summary" view.意思
K-SO的毒性在增加。

8

这是有关jsfiddle内存配置的提示:使用以下URL隔离jsfiddle结果,它将删除所有jsfiddle框架并仅加载您的结果。

http://jsfiddle.net/4QhR2/show/

在阅读以下文档之前,我始终无法弄清楚如何使用时间轴和事件探查器来跟踪内存泄漏。阅读标题为“对象分配跟踪器”的部分后,我可以使用“记录堆分配”工具,并跟踪一些独立的DOM节点。

我通过从jQuery事件绑定切换为使用Backbone事件委托来解决了该问题。据我了解,如果您致电,较新版本的Backbone将自动为您取消绑定事件View.remove()。自己执行一些演示,它们设置有内存泄漏,供您识别。在学习了本文档之后,如果您仍然不明白,请随时在这里提问。

https://developers.google.com/chrome-developer-tools/docs/javascript-memory-profiling


6

基本上,您需要查看堆快照中的对象数。如果两个快照之间的对象数量增加,并且您已经废弃了对象,则可能会发生内存泄漏。我的建议是在代码中寻找不会分离的事件处理程序。


3
例如,如果我查看jsfiddle的堆快照,则在单击“删除”之前,存在的对象远远超过100,000。我应该在哪里寻找jsfiddle的代码实际创建的对象?我认为Window/http://jsfiddle.net/4QhR2/show可能有用,但这只是无穷无尽的功能。我不知道那里发生了什么。
EleventyOne

@EleventyOne:我不会使用jsFiddle。为什么不只是在自己的计算机上创建文件进行测试?
蓝天

1
@BlueSkies我做了一个jsfiddle,所以这里的人可以在同一代码库中工作。但是,当我在自己的计算机上创建文件进行测试时,堆快照中仍然存在50,000+个对象。
EleventyOne

@EleventyOne一个堆快照无法让您知道是否存在内存泄漏。您至少需要两个。
康斯坦丁·迪涅夫

2
确实。我当时强调的是,当存在成千上万个对象时,知道要查找的内容是多么困难。
EleventyOne


3

您也可以在开发人员工具中查看“时间轴”选项卡。记录应用程序的使用情况,并注意DOM节点和事件侦听器的数量。

如果内存图确实表明存在内存泄漏,则可以使用探查器找出泄漏的内容。



2

我第二次建议创建堆快照,它们非常适合检测内存泄漏,chrome可以出色地完成快照。

在我本人的研究项目中,我正在构建一个交互式Web应用程序,该应用程序必须生成在“层”中建立的大量数据,其中许多层将在UI中被“删除”,但由于某种原因,内存并未被删除。被释放,使用快照工具,我能够确定JQuery一直在对象上保留引用(源是当我尝试触发 .load()事件,尽管超出范围但仍保留了引用)。一手掌握这些信息就可以保存我的项目,当您使用其他人的库时,它是一个非常有用的工具,并且存在缠绵的引用使GC无法完成其工作的问题。

编辑:预先计划要执行的操作以最大程度地减少花在快照上的时间,假设可能导致问题的原因并测试每种情况,并在前后进行快照,这也很有用。


0

关于使用Chrome Developer工具识别内存泄漏的一些重要说明:

1)Chrome本身对于某些元素(例如密码和数字字段)存在内存泄漏。https://bugs.chromium.org/p/chromium/issues/detail?id=967438。避免在调试时使用它们,因为它们在搜索分离的元素时会污染您的堆快照。

2)避免将任何内容记录到浏览器控制台。Chrome不会垃圾收集写入控制台的对象,因此会影响您的结果。您可以通过在脚本/页面的开头放置以下代码来抑制输出:

console.log = function() {};
console.warn = console.log;
console.error = console.log;

3)使用堆快照并搜索“分离”以标识分离的DOM元素。通过将对象悬停,您可以访问所有属性,包括idexternalHTML,这些属性可能有助于标识每个元素。 JS Heap Snapshot的屏幕快照,其中包含有关分离的DOM元素的详细信息 如果分离的元素仍然过于笼统而无法识别,请在运行测试之前使用浏览器控制台为其分配唯一的ID,例如:

var divs = document.querySelectorAll("div");
for (var i = 0 ; i < divs.length ; i++)
{
    divs[i].id = divs[i].id || "AutoId_" + i;
}
divs = null; // Free memory

现在,当您确定带有分离元素时,可以说id =“ AutoId_49”,重新加载页面,再次执行上面的代码片段,然后使用DOM inspector或document.querySelector(..)查找id =“ AutoId_49”的元素。 。自然,这仅在页面内容可预测的情况下有效。

我如何运行测试以识别内存泄漏

1)加载页面(抑制控制台输出!)

2)在页面上做可能导致内存泄漏的操作

3)使用开发人员工具拍摄堆快照并搜索“分离”

4)悬停元素以从其idexternalHTML属性中识别它们


另外,禁用缩小/丑化总是一个好主意,因为这会使浏览器中的调试更加困难。
Jimmy Thomsen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.