使用 Node.js 堆快照定位内存泄漏

使用 Node.js 堆快照

你可以为正在运行的应用生成堆快照(Heap Snapshot),再将它加载到 Chrome 开发者工具中,检查特定变量或保留对象所占的内存。还可以比较多份快照,观察它们随时间发生的变化。

警告

生成快照时,主线程中的其他工作都会停止。根据堆中的内容,这个过程甚至可能超过一分钟。快照在内存中构建,因此可能使堆大小翻倍,耗尽全部内存并导致应用崩溃。

如果准备在生产环境生成堆快照,务必确保目标进程即使崩溃,也不会影响应用的可用性。

操作方法

获取堆快照

获取堆快照有多种方式:

  1. 通过检查器(inspector);
  2. 通过外部信号和命令行参数;
  3. 在进程内部调用 writeHeapSnapshot;
  4. 通过检查器协议。

1. 在检查器中使用内存分析功能

适用于所有仍在积极维护的 Node.js 版本。

使用 --inspect 参数启动 Node.js,然后打开检查器。

最简单的方式是将检查器连接到本地运行的进程,进入 Memory(内存)选项卡,生成堆快照。

在 Memory 选项卡中选择生成堆快照。

2. 使用 –heapsnapshot-signal 参数

适用于 v12.0.0 及更高版本。

可以使用命令行参数启动 Node.js,使进程在收到指定信号时生成堆快照。

$ node --heapsnapshot-signal=SIGUSR2 index.js

详细信息请参阅 heapsnapshot-signal 参数的最新文档。

3. 使用 writeHeapSnapshot 函数

适用于 v11.13.0 及更高版本。较早的版本可以使用 heapdump 包。

如果需要为正在工作的进程(例如服务器上的应用)生成快照,可以使用以下方式:

require('node:v8').writeHeapSnapshot();

import { writeHeapSnapshot } from 'node:v8';
writeHeapSnapshot();
JavaScript

文件名选项请参阅 writeHeapSnapshot 文档。

你需要一种无需先停止进程就能触发该调用的方式,例如在 HTTP 请求处理器中调用,或响应操作系统信号。不要公开触发快照的 HTTP 端点;其他人不应能够访问它。

对于 v11.13.0 之前的 Node.js 版本,可以使用 heapdump 包。

4. 通过检查器协议触发堆快照

检查器协议允许从进程外部触发堆快照。

使用这个 API 并不要求实际运行 Chromium 的检查器界面。

下面是一个 Bash 示例,使用 websocat 和 jq 触发快照:

#!/bin/bash
set -e

kill -USR1 "$1"
rm -f fifo out
mkfifo ./fifo
websocat -B 10000000000 "$(curl -s http://localhost:9229/json | jq -r '.[0].webSocketDebuggerUrl')" < ./fifo > ./out &
exec 3>./fifo
echo '{"method": "HeapProfiler.enable", "id": 1}' > ./fifo
echo '{"method": "HeapProfiler.takeHeapSnapshot", "id": 2}' > ./fifo
while jq -e "[.id != 2, .result != {}] | all" < <(tail -n 1 ./out); do
  sleep 1s
  echo "Capturing Heap Snapshot..."
done

echo -n "" > ./out.heapsnapshot
while read -r line; do
  f="$(echo "$line" | jq -r '.params.chunk')"
  echo -n "$f" >> out.heapsnapshot
  i=$((i+1))
done < <(cat out | tail -n +2 | head -n -1)

exec 3>&-
Shell

下面列出了一种可配合检查器协议使用的内存分析工具;这不是完整清单:

  • OpenProfiling for Node.js

通过堆快照定位内存泄漏

比较两份快照可以帮助定位内存泄漏。关键是尽量避免在差异中混入无关信息。以下步骤有助于得到较清晰的快照差异:

  1. 让进程加载所有资源并完成启动初始化,通常最多需要几秒钟。
  2. 开始使用怀疑存在内存泄漏的功能。这一步可能先产生一些初始化分配,它们不一定属于泄漏。
  3. 生成第一份堆快照。
  4. 继续使用该功能一段时间,期间尽量不要运行其他功能。
  5. 再生成一份堆快照。两者之间的差异应主要包含泄漏的内容。
  6. 打开 Chromium/Chrome 开发者工具,进入 Memory 选项卡。
  7. 先加载较早的快照文件,再加载较新的快照文件。
  8. 选中较新的快照,在顶部下拉菜单中将模式从 Summary(摘要)切换为 Comparison(比较)。
  9. 寻找较大的正向增量,并在下方面板中检查导致这些增量的引用。

你可以通过原文链接中的堆快照练习,练习捕获快照和定位内存泄漏。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容