首页
/ Puppeteer Page.captureHeapSnapshot() 详解:JavaScript 堆快照捕获与内存分析实战

Puppeteer Page.captureHeapSnapshot() 详解:JavaScript 堆快照捕获与内存分析实战

2026-09-07 11:11:54作者:余洋婵Anita

本文基于当前仓库(puppeteer-core 25.8.0)官方 API 文档编写,核心内容来自 Page.captureHeapSnapshot()HeapSnapshotOptions,并结合 CDP 实现源码单元测试 纵深展开。

导读

Page.captureHeapSnapshot() 是 Puppeteer 提供的内存(堆)分析入口:它一键捕获当前标签页渲染进程的 JavaScript 堆快照,并直接落盘为 .heapsnapshot 文件。该文件可被 Chrome DevTools 的 Memory 面板或第三方内存分析工具加载,用于定位内存泄漏、分析对象留存路径、诊断高内存占用。本文将以该 API 为中心,讲清方法签名、参数语义、底层 CDP 协议链路、Firefox/WebDriver BiDi 下的支持边界,并给出可直接运行的 Node.js 实战示例与结果验证方法。

方法签名与核心语义

API 声明

根据 TypeScript 定义,该方法属于 Page 类的抽象方法:

abstract captureHeapSnapshot(options: HeapSnapshotOptions): Promise<void>;

要点:

  • 所属类Page,即某个标签页(Tab / Frame 树主入口)的抽象封装;
  • 入参HeapSnapshotOptions,必填;
  • 返回值Promise<void>,方法在快照完整写入文件后 resolve;
  • 语义(官方文档原文):Captures a snapshot of the JavaScript heap and writes it to a file.——捕获 JavaScript 堆的快照并将其写入文件。

从调用者的视角,整个过程被封装为"一条异步方法 + 一个目标文件路径",无需关心 DevTools 协议细节。

参数详解:HeapSnapshotOptions

captureHeapSnapshot() 的参数类型是 HeapSnapshotOptions,其底层定义位于 packages/puppeteer-core/src/api/Page.ts#L707-L712

export interface HeapSnapshotOptions {
  /**
   * The file path to save the heap snapshot to.
   */
  path: string;
}

该接口目前只有一个属性:

属性 类型 必填 说明
path string 保存堆快照的文件路径(无默认值)

实操注意点:

  • 路径语义path 是进程本地文件路径(由运行 Puppeteer 的 Node.js 进程解析并写入),不是浏览器端路径,也不是 URL;
  • 文件命名约定:业界惯例使用 .heapsnapshot 后缀(Chrome DevTools 生成的快照即为此扩展名),但方法本身不校验后缀;
  • 覆盖行为:源码直接用 createWriteStream(options.path) 打开文件写入,若目标路径已存在会被覆盖,请自行处理命名与清理;
  • 目录必须存在:Puppeteer 不会自动创建父目录,若目标目录不存在会导致写流触发 error 事件,从而 reject。

底层实现:CDP 协议调用链

在 Chrome(CDP 协议)后端下,真正执行堆快照捕获的是 packages/puppeteer-core/src/cdp/Page.ts#L888-L917 中的 CdpPage.captureHeapSnapshot() 覆写实现。整条调用链可以拆解为以下阶段:

Node 端写流准备
  └─ environment.value.createWriteStream(options.path)
CDP 域开启与 GC
  ├─ HeapProfiler.enable
  └─ HeapProfiler.collectGarbage
事件订阅(流式接收)
  └─ HeapProfiler.addHeapSnapshotChunk → stream.write(chunk)
触发快照
  └─ HeapProfiler.takeHeapSnapshot({ reportProgress: false })
收尾
  ├─ HeapProfiler.disable(finally 中保证执行)
  └─ stream.end() → 等待 'finish' 事件后 resolve

逐段解读源码(cdp/Page.ts L888-L917):

  1. 创建写流const stream = environment.value.createWriteStream(options.path);,并把 finish/error 事件包装成一个 Promise,用于等待"数据全部落盘"或"写入失败"。
  2. 启用并强制 GC:依次发送 HeapProfiler.enableHeapProfiler.collectGarbage。先收集垃圾再采样,能让快照更真实地反映"存活对象"的占用,减少垃圾对象的干扰。
  3. 流式接收分块:通过 using clientEmitter = new EventEmitter(client); 订阅主目标会话的 HeapProfiler.addHeapSnapshotChunk 事件,每个 chunk 到达后立即写入文件流。这是堆快照文件动辄几十上百 MB 时仍能平稳落盘的关键——数据是分块流式写入而非一次性驻留内存。
  4. 触发快照:发送 HeapProfiler.takeHeapSnapshot 并关闭进度上报(reportProgress: false),让 CDP 侧持续产出快照数据块。
  5. 安全收尾try/finally 中确保调用 HeapProfiler.disable 释放该域;随后 stream.end() 并等待 finish 事件,保证 Promise resolve 时文件已完整写入。

值得留意的是 using 关键字(ES 显式资源管理),clientEmitter 在作用域结束时会自动释放监听器,避免事件订阅泄漏。

支持边界:Chrome 可用,WebDriver BiDi/Firefox 暂不支持

Puppeteer 现已同时支持基于 Chrome DevTools Protocol(CDP)与 WebDriver BiDi 的自动化。但 captureHeapSnapshot() 目前只在 CDP 后端可用

在 WebDriver BiDi 的实现 packages/puppeteer-core/src/bidi/Page.ts#L984-L988 中,该方法被显式标记为不支持:

override async captureHeapSnapshot(
  _options: HeapSnapshotOptions,
): Promise<void> {
  throw new UnsupportedOperation();
}

也就是说:当你通过 puppeteer.launch({protocol: 'webDriverBiDi'}) 连接 Firefox(或 BiDi 模式下的 Chromium)并调用该方法时,会抛出 UnsupportedOperation 异常。判断依据为源码事实:该能力依赖 CDP 的 HeapProfiler 域,而 WebDriver BiDi 尚未暴露等价的堆采样能力。因此在跨浏览器(cross-browser 示例)场景中做内存分析时,应优先选用 CDP 连接的 Chrome 系浏览器。

实战:从启动浏览器到分析堆快照

完整示例

基于上文 API 与实现语义,下面给出一个可直接运行的完整脚本:

import puppeteer from 'puppeteer';
import {mkdtemp} from 'node:fs/promises';
import {tmpdir} from 'node:os';
import path from 'node:path';

const browser = await puppeteer.launch({headless: true});
const page = await browser.newPage();

// 打开一个真实页面并执行若干操作,制造可观测的内存负载
await page.goto('https://example.com', {waitUntil: 'networkidle0'});
await page.evaluate(() => {
  globalThis.__leak = [];
  for (let i = 0; i < 100000; i++) {
    globalThis.__leak.push({id: i, data: new Array(64).join('x')});
  }
});

// 1) 准备目标目录与文件路径(captureHeapSnapshot 不会自动创建目录)
const dir = await mkdtemp(path.join(tmpdir(), 'heap-'));
const snapshotPath = path.join(dir, 'page.heapsnapshot');

// 2) 捕获堆快照
await page.captureHeapSnapshot({path: snapshotPath});
console.log('Heap snapshot saved to:', snapshotPath);

await browser.close();

快照结果的验证与进一步分析

可以用与仓库自带单元测试 test/src/cdp/heapSnapshot.test.ts 相同的思路校验产物有效性。该测试的验证流程是:

await page.captureHeapSnapshot({path: filePath});
expect(fs.existsSync(filePath)).toBe(true);
const content = fs.readFileSync(filePath, 'utf8');
const snapshot = JSON.parse(content);
expect(snapshot.snapshot).toBeDefined();
expect(snapshot.nodes).toBeDefined();
expect(snapshot.edges).toBeDefined();

即:捕获成功后文件必须存在,且内容可被 JSON.parse,并包含 snapshotnodesedges 三个顶层字段——这正是 Chrome 堆快照(.heapsnapshot)的标准结构。你可以直接按此结构编写自己的校验,或把文件交给 DevTools 的 Memory → Load 加载,检索如 __leak 这类全局引用,查看其 Retainers(保留路径)以定位泄漏根因。

失败场景与排查建议

场景 现象 建议
目标目录不存在 Promise reject(写流 error 提前 fs.mkdir(可用 mkdir(dir, {recursive: true})
目标文件已存在 被静默覆盖 用时间戳/唯一后缀命名,如 heap-${Date.now()}.heapsnapshot
BiDi/Firefox 会话中调用 UnsupportedOperation 切换 CDP 协议连接 Chrome 系浏览器
页面内存极大 落盘耗时较长 依赖流式写入,无需担心单次驻留内存;可加大等待超时

与其他内存观测手段的取舍

Page 类还提供了更轻量的 page.metrics()(返回 JSHeapUsedSizeJSHeapTotalSize 等采样指标,见 api/Page.ts 相关注释)。两者定位不同:

  • metrics():秒级、低开销的"读数",适合持续监控堆大小的变化趋势,但不含对象级信息;
  • captureHeapSnapshot():重量级、按需触发的"全景快照",体积大但包含完整对象图,适合问题定位时的深度剖析。

实战中的典型策略是:先用 page.metrics() 或 CDP 侧的 HeapProfiler 采样做趋势判断,确认存在增长后再用 captureHeapSnapshot() 抓取现场快照,对比多个时间点快照(DevTools 支持在多个 .heapsnapshot 间对比)即可找出持续增长的对象类型。

参考链接速查

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.74 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.81 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
595
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
920
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.63 K
1.02 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
518
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
389