专注于
IT技术和业内交流

Chrome DevTools — 内存分析中常见的术语

这个部分介绍了内存分析中常见的术语,即使是在其他语言的内存分析工具中,这些术语也同样有用。

这里所说的术语和概念是用于堆探查器以及相应文档中的。
如果你曾经使用 Java、.Net 或者其它内存分析器,那么该篇的内容对你而言就是一次提升。

对象的大小

请将内存状况想象为一副图片,图中有着一些基本类型(像是数字以及字符串等)和对象(关联数组)。
如果像下面这样将图中的内容用一些相互连接的点来表示,可能有助于你对此的理解:

Visual representation of memory

对象可以通过两种方式来获取内存:

  • 直接通过它本身。

  • 通过包含对其它对象的引用,这样就会阻止垃圾回收器(简称 GC)自动回收这些对象。

当使用 DevTools 中的堆分析器(一种用于查找“配置文件”下的内存问题的工具)的时候,你会发现你所看到的是几列信息。
其中最重要的就是 Shallow Size 以及 Retained Size,不过,这两列究竟意味着什么呢?

Shallow and Retained Size

Shallow size

这是指对象本身获得的内存大小。

典型的 JavaScript 对象会获得一些保留的内存,用于他们的描述以及存储即时产生的值。通常情况下,只有数组和字符串才会有比较明显的浅层大小。不过,字符串和外部数组往往在渲染内存中有它们自己的主存储器,对 JavaScript 堆只露出一点包装后的对象。

渲染内存是指所监视的页面被渲染的过程中使用的内存:原本分配的内存 + 该页面在 JS 堆中的内存 + 所有因为该页面而导致的 JS 堆中其他对象的内存开销。然而,即使是一个小的对象也可以通过阻止垃圾回收器自动回收其他对象来间接保有大量的内存。

Retained size

这是指对象以及其相关的对象一起被删除后所释放的内存大小,并且 GC roots 无法到达该处。

GC roots 是由在从原生代码的 V8 之外引用 JavaScript 对象的时候所创建的句柄(局部或者全局的)构成的。这些句柄可以再堆的快照中 GC roots > Handle scope 以及 GC roots > Global handles 中找到。在没有谈及浏览器实现的细节的情况下,就在本文中说明句柄会令读者感到困惑,故而关于句柄的细节本文不做讲解。事实上,无论 GC roots 还是句柄,都不是你需要担心的东西。

内部的 GC roots 有很多,不过用户对其中的大部分都不感兴趣。从应用程序的角度来说,有下面这么几种 roots:

  • 窗口全局对象(在每一帧中)。在堆快照中,有一个距离域,其包含的是在窗口最短保留路径上的属性引用的数目。
  • 文档 DOM 树是由所有分析该文档时能够到达的 DOM 节点构成的。并不是所有的节点都会有 JS 封装,但是如果他们有封装,那么只要文档还在,这些节点就可以使用。
  • 有些时候,对象会被调试器上下文以及 DevTools 控制台保留(例如,在控制台进行评估后)。清空控制台并且调试器中没有活跃的断点的情况下来创建快照。

下面的内存就是由一个根节点开始的,这个根节点可能是浏览器的 window 对象或者是 Node.js 模块的 Global 对象。你并不需要知道这个对象是如何被回收的。

Root object can't be controlled

任何无法被根节点取得的元素够将被回收。

提示: Shallow 和 Retained size 都用字节来表示数据。

对象的保留树(Objects retaining tree)

像我们前面所说的,堆就是由相互连接的对象构成的网络。在数学的世界中,这种结构称作图(graph)或者内存图。一个图是由节点(Nodes)边(Edges)构成的,而节点又是由边连接起来的,其中节点和边都有相应的标签。

  • 节点(或对象) 是用创建对象的构造函数(constructor)标记的。
  • 边(Edges) 是用属性(properties)名来标记的。

了解如何使用堆探查器来记录资料
之后我们将看见一些吸引眼球的事情,在堆分析器记录中我们可以看到包括 Distance 在内的几栏:Distance 指的是从GC根节点到当前节点的距离。有一种情况是值得探究的,那就是几乎所有同类的对象都有着相同的距离,但是有一小部分对象的 Distance 的值要比其他对象大一些。

Distance from root

主导者(Dominators)

主导者对象是由树形结构组成的,因为每个对象都只有一个主导者。一个对象的支配者不一定直接引用它所主导的对象,也就是说,支配树并不是图的生成树。

在下面的图中:

  • 节点 1 主导节点 2
  • 节点 2 主导节点 3、 4 和 6
  • 节点 3 主导节点 5
  • 节点 5 主导节点 8
  • 节点 6 主导节点 7

Dominator tree structure

在下面的例子中,节点 #3#10 的主导者,但是 #7 节点也在由 GC 到 #10 节点的,每条简单路径上。因此,如果对象 B 存在于从根节点到对象 A 的,每条简单路径上,那么对象 B 就是对象 A 的主导者。

Animated dominator illustration

V8 的细节

在本节中,我们所讲的是对应 V8 JavaScript 虚拟机(V8 VM 或者 VM)的内存方面的话题。这些内容对于理解堆快照为何是上面所看到的那个样子很有帮助。

JS对象的表现形式

JavaScript 中有三种主要类型:

  • 数字(Numbers)(比如,3.14159..)
  • 布尔值(Booleans)(true 或者 false)
  • 字符串(Strings) (比如 “Werner Heisenberg”)

这些类型在树中都是叶子节点或者终结节点,并且它们不能引用其它值。

数字类型

可以像下面这样存储:

  • 相邻的 31 位整数值,被称为 small integers (SMIs)
  • 被称为堆数字(heap numbers)的堆对象。堆数字用于存储不适合 SMI 形式的值,比如浮点类型(doubles),或者是需要封装(boxed)的值,比如设置其属性值的类型。

字符串

可以被存储在:

  • 虚拟机的堆中
  • 外部的渲染内存(renderer’s memor)。也就是当创建或者使用一个封装后的对象时需要使用的外部存储器,比如,脚本资源以及其他从网上接收而不是赋值到虚拟机堆中存储的内容。
    新的 JavaScript 对象的内存是由特定的 JavaScript 堆(或者说 VM 堆)分配的。这些对象由 V8 垃圾回收器管理,并且只要存在一个对他们的强引用就不会被回收。

本地对象(Native objects)

指的是不在 JavaScript 堆中存储的一切对象。本地对象和堆对象相反,其生存周期不由 V8 垃圾回收器管理,并且只能通过封装它们的 JavaScript 对象来使用。

Cons string

Cons string 是一个保存了成对字符串的对象,并且该对象会将字符串拼接起来,最后的结果是串联后的字符串。拼接后的 cons string 的内容只有在需要的时候才会出现。一个比较好的例子就是,如果想获取某个字符串的子串,就必须利用函数进行构建。
举个例子,如果你将 ab 对象串联,那么你将获得一个字符串(a,b) 用于表示拼接后的结果。如果你之后又加入了一个对象 d,那么你将活的另一个字符串((a,b),d)。

数组(Arrays)

一个数组就是有着数字键的对象。他们广泛应用在 V8 VM 中,用于存储大量数据。在字典这样的数据结构中键值对的集合就是利用数组来备份的。

一个典型的用于存储的 JavaScript 对象可以是下列两种数组类型之一:

  • 命名的属性
  • 数字元素

如果想要存储的是少量的属性,那么它们可以直接在 JavaScript 对象中存储。

Map

一个对象,用于描述对象及其布局的种类。举个例子,maps 用于描述快速属性访问的隐式对象结构。

对象组

每个本地的对象组都是由保持彼此相互引用的对象组成的。以一个 DOM 子树为例,在该树中,每一个节点都一个指向父节点的连接,以及指向孩子节点和兄弟节点的链接,由此,所有的节点连成了一张图。需要注意的是,本地对象并不会在 JavaScript 堆中出席那,所以它们的大小是 0。相应的,对于每个要使用本地对象都会创建一个对应的封装对象。

每个封装对象都含有一个对相应的本地对象的引用,这是为了能够将命令重定向到本地对象上。而对象组则含有这些封装的对象,但是,这并不会造成一个无法回收的死循环,因为垃圾回收器会自动释放不在引用的封装对象。但是一旦忘记了释放某个封装对象就可能造成整个组以及相关封装对象都无法被释放。

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

未经允许,不得转载本站任何文章:代码山 » Chrome DevTools — 内存分析中常见的术语

分享到:更多 ()

专注品牌化高端网站建设

商务服务联系我们