专注于
IT技术和业内交流

Chrome DevTools — 如何记录堆快照

学习如何用Chrome DevTools 堆探查器,记录堆快照并查找内存泄漏。

Chrome DevTools 堆探查器显示由你页面 JavaScript 对象和相关的 DOM 节点分配的内存 (请参见Objects retaining tree)。使用它来获取 JS 堆快照、 分析内存图、 比较快照,查找内存泄漏。

快照(Take a snapshot)

Profiles面板中,选择Take Heap Snapshot,然后单击Start或按Cmd + E or Ctrl + E:

Select profiling type

最初快照是存在渲染内存中的,当你点击快照图标来查看它的时候,它将会被传输到 DevTools 中。

当快照载入到 DevTools 中并被解析后,快照标题下面会出现一个数字,该数字表示所有可访问的 JavaScript 对象的总大小

Total size of reachable objects

清除快照(Clear snapshots)

点击清除所有配置( Clear all profiles)图标可以清除快照(DevTools 中和渲染内存中都会删除掉):

Remove snapshots

直接关闭 DevTools 窗口并不会删除渲染内存中的配置文件。当重新打开 DevTools 窗口的时候,所有之前生成的快照都会在快照列表中出现。

示例: 尝试使用分散对象这个示例并使用堆分析器来进行分析。您应该能看到对象的分配次数。

查看快照

不同的任务中使用不同角度查看快照:

摘要视图(Summary view) 按构造函数名称分组显示对象。用它来基于类型分组追捕对象 (和内存使用) 。它对追踪 DOM 泄漏特别有帮助。

比较视图(Comparison view) 显示两个快照之间的差异。用它来比较两个 (或更多) 内存快照的操作之前和之后。检查被释放的被引用的对象让您确认内存泄漏的原因。

包含视图(Containment view) 允许堆内容的探索。它提供更好的对象结构的视图,帮助分析全局命名空间 (window)周围是什么对象在引用。用它来分析闭包或对象的更深层次对象。

主导者视图(Dominators view) 显示主导者树,并可用于发现积累(accumulation?)点。此视图可以帮助确认对象在删除/垃圾回收之前是否有外部引用任在工作。

视图之间进行切换,请使用视图底部的下拉框:

Switch views selector

摘要视图(Summary view)

最开始的时候,快照是在摘要视图中打开的,显示了对象的整体情况,并且该视图可以展开以显示实例信息:

Summary view

顶级入口是 “total” 行,他们展示了:

  • Constructor,表示所有用这个构造器创建的对象。
  • 对象实例的数量 显示在 # 这一列下。
  • Shallow size 这一列显示了当前构造器创建的所有对象的 shallow size 总和。shallow size是对象本身的内存的大小(一般,数组和字符串有较大的shallow size)。另请参见对象大小
  • Retained size 这一列显示相同的对象集所对应的最大 retained size。删除对象 (依赖对象不再可达时) 可以释放的内存的大小被称为retained size。另请参见对象大小
  • Distance 显示了从根节点开始,从节点的最短路径到达当前节点的距离。

像上图那样展开 total line 之后,其所有的实例都会显示出来。对于每个实例,它的 shallow size 和 retained size 都会在相应列中展示出来。
在 @ 字符后面的数字就是对象的 ID,该 ID 允许你在每个对象的基础上比较堆的快照。

请记住,黄色的对象表示有 JavaScript 对象引用了它们,而红色的对象是指从一个黄色背景节点引用的分离节点(detached nodes)。

在堆分析器中不同的Constructor对应什么功能?

Constructor groups

  • (global property) – 全局对象(像 ‘window’)和引用它的对象之间的中间对象。如果一个对象由构造函数Person生成并被全局对象引用,那么引用路径就是这样的:[global] > (global property > Person。
    这跟一般的直接引用彼此的对象不一样。我们用中间对象是有性能方面的原因,全局对象改变会很频繁,非全局变量的属性访问优化对全局变量来说并不适用。

  • (roots) – constructor中roots的内容引用它所选中的对象。它们也可以是由引擎自主创建的一些引用。
    这个引擎有用于引用对象的缓存,但是这些引用不会阻止引用对象被回收,所以它们不是真正的强引用。

  • (closure) – 一些函数闭包中的一组对象的引用

  • (array, string, number, regexp) – 一组属性引用了Array,String,Number或正则表达式的对象类型

  • (compiled code) – 简单来说,所有东西都与compoled code有关。Script像一个函数,但其实对应了<script>的内容。SharedFunctionInfos (SFI)是函数和compiled code之间的对象。函数通常有内容,而SFIS没有

  • HTMLDivElement, HTMLAnchorElement, DocumentFragment 等 – 你代码中对elements或document对象的引用。

示例︰ 请尝试此演示页面以了解如何使用摘要视图。

比较视图(Comparison view)

这个视图用于比较不同的快照,这样,你就可以通过比较它们的不同之处来找出出现内存泄露的对象。
想要弄清楚一个特定的程序是否造成了泄露(比如,通常是相对的两个操作,就像是打开文档,然后关闭它,是不会留下内存垃圾的),你可以尝试下列步骤:

  1. 在执行操作前先生成一份快照。
  2. 执行操作(该操作涉及到你认为出现内存泄露的页面)。
  3. 执行一个相对的操作(做出相反的交互行为,并重复多次)。
  4. 生成第二份快照然后将视图切换到比较视图,将它与第一份快照对比。

在比较视图中,两份快照间的不同之处会展示出来。当展开一个总入口时,添加以及删除的对象实例会显示出来:

Comparison view

示例: 请尝试这个演示页面来了解如何使用比较视图来检测内存泄露。

包含视图(Containment view)

包含视图本质上就像是你的应用程序对象结构的俯视图。它使你能够查看到函数闭包内部,甚至是观察到那些组成 JavaScript 对象的虚拟机内部对象,借助该视图,你可以了解到你的应用底层占用了多少内存。

这个视图提供了多个接入点:

DOMWindow objects – 这些是被认作“全局”对象的对象。
GC roots – 虚拟机垃圾回收器实际实用的垃圾回收根节点。
Native objects – 指的是“推送”到 JavaScript 虚拟机内以实现自动化的浏览器对象,比如,DOM 节点,CSS 规则

下面是常见的包含视图的例子:

Containment view

示例: 通过这个页面来尝试如何在该视图中找到闭包和事件处理器。

关于闭包的小提示

为函数命名有助于你在快照中分辨不同的闭包。举个例子,下面这个函数没有命名:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

而下面这个是命名后的函数:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

Name functions to distinguish between closures

示例: 尝试一下这个例子来分析闭包对内存的影响。你可能会对下面这个例子感兴趣,它可以让你深入了解堆内存分配

主导视图(Dominators view)

主导视图显示了堆图的主导树,从形式上来看,主导视图有点像是包含视图,但是缺少了某些属性。
这是因为主导者对象可能会缺少对它的直接引用,也就是说,主导树不是生成树。

注意: 在 Chrome Canary 中,主导视图可以在 Settings > Show advance snapshots properties 中启用,重启浏览器之后就可以选择主导视图了。

Dominators view

示例: 尝试这个演示来看看你能不能找到积累点。随后可以尝试运行 retainning paths and dominators

查看代码颜色

对象的属性以及属性值属于不同类型并且有着相应的颜色。每个属性都会有四种类型之一:

  • a:property – 有名称的常规属性,通过 .(点)操作符或者 [](方括号)符号来访问,例如 [“foo bar”];
  • 0:element – 有数字下标的常规属性,使用 [](方括号)来访问。
  • a:context var – 函数上下文中的某个变量,在相应的函数闭包中使用其名字就可以访问。
  • a:system prop – 由 JavaScript 虚拟机添加的属性,在 JavaScript 代码中无法访问。

被命名为 System 这样的对象是没有相应的 JavaScript 类型的。他们是 JavaScript 虚拟机的对象系统的一部分。V8 将大多数内部对象分配到和用户 JS 对象相同的堆中,所以这些都只是 V8 内部内容。

查找特定对象

在集堆中查找对象可以使用 Ctrl + F 查找对象ID.

发现 DOM 内存泄露

该工具的一大特点就是它能够显示浏览器本地对象(DOM 结点,CSS 规则)以及 JavaScript 对象间的双向依赖关系。
这有助于发现因为忘记分离 DOM 子树而导致的不可见的泄露。

DOM 泄露肯能比你想象中的要多。考虑下面这个例子 – 什么时候 #tree 会被回收?

  var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW can be #tree GC

#leaf 包含了对其父亲(父节点)的引用并递归到 #tree,所以只有当 leafRef 失效的时候 #tree 下的整棵树才能被回收。

DOM subtrees

示例: 尝试这个例子有助于你理解 DOM 节点中哪里容易出现泄露以及如何找到它们。你也可以继续尝试后面这个例子DOM 泄露比想象的要更多

想要了解更多关于 DOM 泄露以及内存分析的基础内容,请参阅 Gonzalo Ruiz de Villa 编写的Finding and debugging memory leaks with the Chrome DevTools

示例: 尝试这个示例来体验分离(detached)的 DOM 树。

本文由@W翻译、@海欧校验,感谢大家的参与.如有错译、漏译请留言我们会及时更正。

未经允许,不得转载本站任何文章:代码山 » Chrome DevTools — 如何记录堆快照

分享到:更多 ()

专注品牌化高端网站建设

商务服务联系我们