本部分介绍了内存分析中常用的术语,适用于适用于不同语言的各种内存性能分析工具。
本文中介绍的术语和概念均与 Chrome 开发者工具堆性能分析器相关。如果您曾使用过 Java、.NET 或其他内存性能分析器,则可以将此部分作为复习内容。
对象大小
将内存视为包含基元类型(例如数字和字符串)和对象(关联数组)的图。它在视觉上可以表示为包含多个相互关联的点的图表,如下所示:

对象可以通过以下两种方式占用内存:
- 直接由对象本身进行调用。
- 隐式地保留对其他对象的引用,从而防止这些对象被垃圾回收器(简称 GC)自动处置。
在 DevTools 中使用堆性能分析器(用于调查内存面板中发现的内存问题的工具)时,您可能会看到几个不同的信息列。其中最引人注目的是浅层大小和保留大小,但它们分别代表什么?

浅层大小
这是对象本身占用的内存大小。
典型的 JavaScript 对象会预留一些内存来存储其说明和立即值。通常,只有数组和字符串可以具有较大的浅层大小。不过,字符串和外部数组的主要存储空间通常位于渲染程序内存中,并且只在 JavaScript 堆上公开一个小型封装容器对象。
渲染程序内存是指用于渲染受检页面的进程的所有内存:原生内存 + 页面的 JS 堆内存 + 由页面启动的所有专用工作器的 JS 堆内存。不过,即使是小对象,也可以通过阻止自动垃圾回收进程处置其他对象,间接占用大量内存。
保留的大小
这是在删除对象本身及其无法从 GC 根访问的依赖对象后释放的内存大小。
GC 根由在从原生代码引用 V8 之外的 JavaScript 对象时创建的句柄(本地或全局)组成。您可以在堆快照的 GC 根 > Handle 作用域和 GC 根 > 全局句柄下找到所有此类句柄。如果本文档中仅介绍了句柄,而没有深入探讨浏览器实现的细节,可能会造成混淆。您无需担心 GC 根和句柄。
存在许多内部 GC 根,其中大多数对用户而言并不重要。从应用的角度来看,根有以下几种:
- 窗口全局对象(在每个 iframe 中)。堆快照中有一个距离字段,该字段是从窗口到最短保留路径上的属性引用数量。
- 文档 DOM 树,由遍历文档可访问的所有原生 DOM 节点组成。其中有些可能没有 JS 封装容器,但如果有,则封装容器会在文档有效期间保持有效。
- 有时,调试程序上下文和开发者工具控制台可能会保留对象(例如在控制台评估后)。创建堆快照,并确保控制台清晰且调试器中没有有效断点。
内存图从根开始,该根可以是浏览器的 window 对象,也可以是 Node.js 模块的 Global 对象。您无法控制此根对象的 GC 方式。

从根无法访问的任何内容都会被 GC。
对象保留树
堆是相互关联的对象网络。在数学领域,这种结构称为图或内存图。图由通过边连接的节点构建而成,并且这两者都已指定标签。
- 节点(或对象)使用用于构建它们的构造函数的名称进行标记。
- 边使用属性的名称进行标记。
了解如何使用堆性能分析器记录配置文件。在以下堆性能分析器记录中,我们可以看到一些引人注目的内容,包括距离:与 GC 根的距离。如果同一类型的几乎所有对象都在相同的距离,而少数对象的距离较大,则值得进行调查。

主导广告系列
由于每个对象只有一个直接支配对象,因此支配对象对象由树结构组成。对象的支配对象可能缺少对其支配对象的直接引用;也就是说,支配对象的树不是图的生成树。
在下图中:
- 节点 1 优于节点 2
- 节点 2 支配节点 3、4 和 6
- 节点 3 对节点 5 具有主导地位
- 节点 5 对节点 8 具有主导作用
- 节点 6 对节点 7 具有主导作用

在以下示例中,节点 #7、#3 和 GC 是 #10 的支配节点,因为它们存在于从根节点 (GC) 到 #10 的每条路径中。具体而言,#7 是 #10 的直接支配项,因为它是从 GC 到 #10 路径上最近的支配项。

V8 具体信息
在分析内存时,了解堆快照为何呈现特定形式会很有帮助。本部分介绍了一些与 V8 JavaScript 虚拟机 (V8 VM 或 VM) 对应的内存相关主题。
JavaScript 对象表示法
有三种基元类型:
- 数字(例如3.14159..)
- 布尔值(true 或 false)
- 字符串(例如'Werner Heisenberg')
它们不能引用其他值,并且始终是叶节点或终止节点。