使用 Chrome 查找 JavaScript 内存泄漏

我创建了一个非常简单的测试用例,它创建了一个 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 内存泄漏

使用 DevTools

代表了一些更强大的材料,从我所看到的。然而,除了介绍 3快照技巧的概念,我发现他们提供的实践知识非常少(对于像我这样的初学者)。“使用 DevTools”教程不能通过真实的例子来实现,所以它对事物的模糊和概念性描述没有太大的帮助。至于“ Gmail”的例子:

所以你找到了漏洞,然后呢?

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

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

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

  • 使用 Closure? 启用适当的现有标志(即 goog.events. Listener.ENABLE _ MONITORING)在构建期间设置 creationStack 属性

我发现自己更困惑后,阅读,而不是更少。而且,再一次,它只是告诉我 的东西,而不是 怎么做做他们。在我看来,所有这些信息要么太模糊,要么只对那些已经理解过程的人有意义。

其中一些更具体的问题已在下文的 @ Jonathan Naguin 的回答中提出。

77075 次浏览

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

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

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

这里有一个关于 jsfiddle 内存分析的技巧: 使用以下 URL 来隔离 jsfiddle 结果,它删除了所有的 jsfiddle 框架并只加载结果。

Http://jsfiddle.net/4qhr2/show/

我一直不知道如何使用时间轴和分析器来跟踪内存泄漏,直到我阅读了下面的文档。在阅读了题为“对象分配跟踪器”的部分后,我能够使用“记录堆分配”工具,并跟踪一些分离的 DOM 节点。

我通过从 jQuery 事件绑定切换到使用 Backbone 事件委托解决了这个问题。我的理解是,如果您调用 View.remove(),新版本的 Backbone 将自动解除对事件的绑定。自己执行一些演示,它们设置了内存泄漏,以便您识别。如果您在学习了本文档之后仍然没有得到答案,可以在这里随意提问。

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

找到内存泄漏的一个好的工作流程是 三张快照技术,它最初被 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;
}

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

文件:

我第二个建议是采用堆快照,它们非常适合检测内存泄漏,chrome 在快照方面做得非常好。

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

编辑: 提前计划将要执行的操作也很有用,这样可以最小化花在快照上的时间,假设可能导致问题的原因,并测试每个场景,在前后进行快照。

你可能还想读一下:

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

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

这里有一个来自 Google 的介绍视频,对于发现 JavaScript 内存泄漏非常有帮助。

Https://www.youtube.com/watch?v=l3ugr9bjqis

关于使用 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 元素。通过悬停对象,您可以访问包括 身份证外部 HTML在内的所有属性,这些属性可以帮助识别每个元素。 Screenshot of JS Heap Snapshot with details about detached DOM element 如果分离的元素仍然过于通用而无法识别,那么在运行测试之前,使用浏览器控制台为它们分配唯一的 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)悬停元素,根据它们的 身份证外部 HTML属性来识别它们