170

我创建了一个非常简单的测试用例,它创建一个 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();

但是,我不清楚如何使用谷歌浏览器的分析器来验证事实是否如此。堆分析器快照上显示了无数的东西,我不知道如何解码好/坏。到目前为止,我看到的教程要么只是告诉我“使用快照分析器”,要么给我一个关于整个分析器如何工作的非常详细的宣言。是否可以仅将分析器用作工具,还是我真的必须了解整个事物是如何设计的?

编辑:像这样的教程:

Gmail 内存泄漏修复

使用开发工具

据我所见,它们代表了一些更强的材料。但是,除了介绍3 Snapshot Technique的概念之外,我发现它们在实践知识方面提供的很少(对于像我这样的初学者)。'使用 DevTools' 教程没有通过一个真实的例子来工作,所以它对事物的模糊和一般的概念描述并没有太大的帮助。至于“Gmail”示例:

所以你发现了一个漏洞。怎么办?

  • 在 Profiles 面板的下半部分检查泄漏对象的保留路径

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

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

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

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

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

4

9 回答 9

215

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

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

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

  • 运行 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;
}

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

文档:

于 2013-11-01T12:37:21.333 回答
8

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

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

在阅读以下文档之前,我一直无法弄清楚如何使用 Timeline 和 Profiler 来追踪内存泄漏。阅读标题为“对象分配跟踪器”的部分后,我能够使用“记录堆分配”工具,并跟踪一些分离的 DOM 节点。

我通过从 jQuery 事件绑定切换到使用 Backbone 事件委托解决了这个问题。据我了解,如果您调用View.remove(). 自己执行一些演示,它们设置了内存泄漏供您识别。如果您在学习本文档后仍然没有得到它,请随时在这里提问。

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

于 2013-10-31T16:06:17.123 回答
6

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

于 2013-10-27T17:33:30.333 回答
5

谷歌有一个介绍视频,对发现 JavaScript 内存泄漏非常有帮助。

https://www.youtube.com/watch?v=L3ugr9BJqIs

于 2014-11-14T07:59:59.637 回答
3

您可能还想阅读:

http://addyosmani.com/blog/taming-the-unicorn-easing-javascript-memory-profiling-in-devtools/

它解释了 chrome 开发人员工具的使用,并就如何使用堆快照比较和可用的不同 hep 快照视图来确认和定位内存泄漏提供了一些分步建议。

于 2014-07-15T09:56:43.107 回答
3

您还可以查看开发人员工具中的 Timeline 选项卡。记录您的应用程序的使用情况并密切关注 DOM 节点和事件侦听器计数。

如果内存图确实表明内存泄漏,那么您可以使用分析器找出泄漏的内容。

于 2013-10-30T08:35:52.800 回答
2

我支持拍摄堆快照的建议,它们非常适合检测内存泄漏,chrome 在快照方面做得很好。

在我攻读学位的研究项目中,我正在构建一个交互式 Web 应用程序,该应用程序必须生成大量构建在“层”中的数据,其中许多层将在 UI 中“删除”,但由于某种原因内存没有被释放,使用快照工具我能够确定 JQuery 一直在对象上保留一个引用(源是当我试图触发一个.load()事件时,尽管超出了范围,仍然保留了引用)。手头的这些信息单独保存了我的项目,当您使用其他人的库时,它是一个非常有用的工具,并且您会遇到延迟引用阻止 GC 完成其工作的问题。

编辑:提前计划您将要执行的操作以最大限度地减少快照所花费的时间,假设可能导致问题的原因并测试每个场景,在之前和之后制作快照也是有用的。

于 2013-11-01T15:20:05.770 回答
1

关于使用 Chrome 开发人员工具识别内存泄漏的几个重要注意事项:

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 元素。通过悬停对象,您可以访问所有属性,包括idouterHTML,这可能有助于识别每个元素。 JS 堆快照的屏幕截图,其中包含有关分离的 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 检查器或 document.querySelector(..) 找到 id="AutoId_49" 的元素. 当然,这仅在您的页面内容是可预测的情况下才有效。

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

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

2) 在页面上做一些可能导致内存泄漏的事情

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

4) 悬停元素以从它们的idouterHTML属性中识别它们

于 2020-01-14T07:41:54.187 回答
-1

使用 2021 年可用的工具在此处添加我的 2 美分:https ://yonatankra.com/how-to-profile-javascript-performance-in-the-browser/

这里有一个简短的视频版本:https ://yonatankra.com/detect-memory-leak-with-chrome-dev-tools

于 2021-02-15T07:33:09.017 回答