使用 Chrome 查找 JavaScript 内存泄漏

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

代码的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在下面的回答中已经提出。


答案 1

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

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

对于您的示例,我调整了代码以显示此过程(您可以在此处找到它),从而将主干视图的创建延迟到“开始”按钮的单击事件。现在:

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

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

您会注意到几种不同颜色的节点。红色节点没有从 Javascript 到它们的直接引用,但它们处于活动状态,因为它们是分离的 DOM 树的一部分。树中可能有一个节点从Javascript引用(可能是作为闭包或变量),但巧合的是,它阻止了整个DOM树被垃圾回收。

enter image description here

然而,黄色节点确实有来自Javascript的直接引用。在同一分离的 DOM 树中查找黄色节点,以查找 Javascript 中的引用。应该有一个从 DOM 窗口到元素的属性链。

在您的特定环境中,您可以看到一个标记为红色的HTML Div元素。如果展开该元素,您将看到由“cache”函数引用的元素。

enter image description here

选择该行,在控制台中键入 $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;
}

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

文档:


答案 2

以下是有关 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