跳到主要内容

Chrome DevTools Memory

2025年01月06日
柏拉文
越努力,越幸运

一、认识


Chrome DevTools Memory 提供强大的工具来分析和排查内存泄漏,检测内存泄漏,分析内存分配,优化内存使用。主要通过 内存快照(Heap Snapshot实时内存监控 等功能。

二、操作


一、打开 Chrome DevTools: 首先,确保你已经打开了 Chrome 浏览器的开发者工具。你可以通过右键点击页面元素并选择 检查 或按 F12 来打开开发者工具。在开发者工具中,切换到 Memory 面板,它专门用于内存分析。

二、录制内存快照(Heap Snapshot: 内存快照用于记录应用程序在特定时刻的内存使用情况,并可以帮助识别持续增长的对象。

  1. Chrome DevTools 中,点击顶部菜单栏的 Memory 面板。

  2. Memory 面板中,选择 Heap Snapshot 模式。

  3. 点击 Take Snapshot 按钮,生成一份内存快照。

  4. 生成的快照文件会显示在左侧列表中,包含堆内存中所有对象的详细信息。

  5. 在代码运行一段时间后,再次点击 Take Snapshot 生成另一个快照。

三、模拟泄漏


假设有一个简单的内存泄漏示例: 全局变量 leaks 数组被持续地推入新对象(如 push 操作),而旧对象没有被释放, 每次请求造成内存持续增加。但是没有释放的操作。

const leaks = [];
setInterval(() => {
leaks.push(new Array(1000000).fill('*')); // 内存泄漏
}, 5000);

1. 打开 Chrome DevTools Memory

2. 录制内存快照: 一般而言,记录内存快照越多,越容易发现内存泄漏的点。

  1. 启动程序录制快照: 在程序启动后,点击 Take Snapshot,记录初始内存情况。

  2. 访问多次应用程序: 代码执行,内存泄漏触发, 记录第二次内存快照

  3. 再多次访问应用程序: 代码执行,内存泄漏触发, 记录第三次内存快照

  4. 再多次访问应用程序: 代码执行,内存泄漏触发, 记录第四次内存快照

四、快照分析


4.1 比较快照

比较多个内存快照: 内存使用量从 5.5 MB → 37.7 MB → 85.7 MB,说明内存占用持续增长。这种情况通常意味着存在内存泄漏,特别是当没有释放的对象持续堆积时。

从截图中可以看到左侧有 3 个快照:

Snapshot 15.5 MB
Snapshot 237.7 MB
Snapshot 385.7 MB

4.2 Summary 定位可疑对象

看这个视图的时候一般会先对 Retained Size 进行排查,然后观察其中对象的大小与数量,有经验的工程师,可以快速判断出某些对象数量异常。在这个视图中除了关心自己定义的一些对象之外, 一些容易发生内存泄漏的对象也需要注意如:

  • TCP

  • Socket

  • EventEmitter

  • global

4.3 Comparison 定位可疑对象

如果通过 Summary 视图, 不能定位到问题这时我们一般会使用 Comparison 视图。通过这个视图我们能对比两个堆快照中对象个数、与对象占有内存的变化; 通过这些信息我们可以判断在一段时间(某些操作)之后,堆中的对象与内存变化的数值,通过这些数值我们可以找出一些异常的对象。通过这些对象的名称属性或作用可以缩小我们内存泄漏的排查范围。

Comparison 视图中选择两个堆快照,并在它们之间进行比较。您可以查看哪些对象在两个堆快照之间新增,哪些对象在两个堆快照之间减少,以及哪些对象的大小发生了变化。Comparison 视图还允许查看对象之间的关系,以及对象的详细信息,如类型、大小和引用计数。通过这些信息,可以了解哪些对象是导致内存泄漏的原因。在 Comparison 视图中查找可疑对象,用于比较当前快照和之前快照的内存变化。主要关注 Comparison.Delta 对象数量的净增量和 Comparison.Size Delta 对象分配的内存变化量。

Array @104125 和其他几个数组显示 New: 13Delta: +13,并且总分配的内存是 32,000,000(32 MB)。这些数组占据了大部分内存,说明它们是内存增长的主要来源。关键是这些数组 持续增长且没有释放,表明这里可能存在泄漏。

4.4 Comparison 定位可疑引用链

其实可以找到一些代码中的变量,如下所示:

[6] in Array @89457
leaks in system / Context @89455 80 000 66493 %

说明 leaks 在可疑对象中占比非常大。

五、DevTools Comparison


Comparison 用于将当前快照与之前的某个快照进行比较,显示内存对象的增量和变化。通过这个视图可以直观地看到哪些对象被分配了新的内存,但没有被释放(即内存持续增加)。

5.1 Constructor

Constructor 显示内存中对象的类型或构造函数(Constructor)。比如 ArrayObjectNodeDetached Node 等。每一行表示一种类型的对象集合。

5.2 New

# New: 从上一个快照到当前快照,新分配的对象数量。# New 可以观察到观察内存中哪些类型的对象正在快速分配。例如,如果 Array# New 值持续增加但没有减少,说明新数组对象在不断创建,但未被释放。可以用来定位新增对象的来源。

5.3 Deleted

# Deleted: 从上一个快照到当前快照,被删除的对象数量。# Deleted 可以观察垃圾回收器(GC)释放了哪些对象。如果某类对象的 # Deleted0,说明这类对象没有被释放,可能导致内存泄漏。可以用来分析哪些对象未被正确清理或回收。

5.4 Delta

# Delta: # New# Deleted 的差值,表示对象数量的净增量。如果为正值,表示对象数量增加了;如果为负值,表示对象数量减少了;如果为 0, 表示对象数量没有变化。可以用来快速定位那些对象数量持续增长的类型。如果某类对象的 # Delta 持续为正,说明存在内存泄漏的可能。

5.5 Alloc. Size

Alloc. Size: 表示对象分配的总内存大小。显示某类对象总共占用了多少内存。通过这个值,可以快速定位占用内存较大的对象类型。可以用来找出高内存占用的对象,如大型数组或字符串。

5.6 Freed Size

Freed Size: 表示在当前快照中,被垃圾回收器(GC)释放的内存大小。观察 GC 是否正常回收了不再需要的对象。如果某类对象的 Freed Size0,但内存持续增长,说明 GC 未能回收这些对象,可能存在引用问题。可以用来判断 GC 的清理效果,找出无法回收的对象。

5.7 Size Delta

Size Delta: 表示对象分配的内存变化量。如果为正值,内存增加; 如果为负值,内存减少; 如果为 0, 内存没有变化。快速查看哪些对象类型导致了内存的增长。如果某类对象的 Size Delta 持续为正,说明存在泄漏。

5.8 Retainers

Retainers 面板: 底部区域,显示 引用链(Retainers,显示某个对象被哪些对象引用,为什么无法被回收。通过 Retainers 面板可以找到泄漏对象的根源。例如,一个数组对象被某个全局变量引用,导致无法释放。可以找出对象被哪些变量或函数引用,分析内存泄漏的原因。

Retainers 指标如下:

  1. Distance: 从根对象(如全局变量、堆栈根节点)到当前对象的引用距离。数值越小,表示对象离根引用越近,越可能无法被回收。

  2. Shallow Size: 当前对象本身占用的内存大小。显示单个对象的内存消耗。用于分析大对象是否导致了内存泄漏。

  3. Retained Size: 如果该对象被保留,它所占用的总内存,包括它所引用的对象的内存。Retained Size 大的对象通常是导致内存泄漏的主要原因。找到这些对象后,可以通过代码分析为什么它们没有被释放。