- 开发工具
- 测试
- CLI
- MCP 服务
【免费下载链接】memlab
A framework for finding JavaScript memory leaks and analyzing heap snapshots
本文基于 memlab 官方教程 03-find-leak.md 展开:当内置泄漏检测器(detached DOM 元素与未卸载的 React Fiber 节点)漏报内存泄漏时,如何通过memlab find-leaks --trace-all-objects把目标交互分配出的所有未释放对象都当作泄漏来追踪,并利用--trace-object-size-above、--ignore-leak-cluster-size-below等参数控制输出噪声。读完本文,你将掌握完整的“运行场景—定位泄漏—降噪—聚类解读”工作流,并能对照 memlab 源码 理解每个参数在底层到底改了什么配置。
一、为什么默认检测器可能不报泄漏
memlab 默认只报告由内置泄漏检测器识别出的高置信度内存泄漏。这些检测器会寻找两类典型泄漏:
- detached DOM 元素:已从文档树摘除但仍被引用链保活的 DOM 节点;
- 未卸载的 React Fiber 节点:组件卸载后 Fiber 对象仍然存活。
如果泄漏对象不属于这两类(例如一个被闭包捕获的大数组、一个持续增长的全局集合),默认检测就可能少报甚至完全漏报。
从源码结构看,泄漏判定是一条规则链:HeapAnalyzer.filterLeakedObjects()内部使用LeakObjectFilter(LeakObjectFilter.ts),按顺序执行 LeakFilterRuleList.ts 中登记的规则——FilterUserTaggedLeaks(用户heapTag标记的泄漏)、FilterByExternalFilter(场景文件里自定义的leakFilter)、FilterTrivialNode、FilterHermesNode、FilterOverSizedNodeAsLeak、FilterUnmountedFiberNode、FilterDetachedDOMElement、FilterStackTraceFrame、FilterXMLHTTPRequest。任何规则判定为LEAK即上报,NOT_LEAK即排除。默认情况下,FilterOverSizedNodeAsLeak规则处于“未启用”状态,所以本教程的核心思路就是显式启用它,让所有存活的目标交互分配对象进入泄漏候选集。
二、准备工作:运行场景并指定快照来源
首先在不指定任何泄漏过滤器的场景文件下运行 memlab,让框架应用内置检测器。若检测不到任何泄漏,再按下面的流程兜底排查:
memlab run --scenario ~/memlab/scenarios/unbound-object.js这里unbound-object.js是一个标准的 Puppeteer 场景文件(IScenario接口的实现,接口定义见 IScenario)。仓库自带的示例场景位于 packages/e2e/static/example/scenarios/,可作为编写action/back/url函数的参考。
memlab run结束后会在工作目录生成三份.heapsnapshot:
- baseline:目标交互之前的堆快照;
- target:执行目标交互(
action)后的堆快照; - final:执行回退交互(
back)并等待 GC 后的堆快照。
memlab find-leaks支持三种方式指定这些输入(见 CheckLeakCommand.ts 的文档字符串),三者只能选其一:
--baseline/--target/--final分别指定三个快照文件路径;--snapshot-dir指定包含三个快照文件的目录(memlab 按文件名字母顺序推断 baseline、target、final);--work-dir指定memlab run或memlab snapshot生成的输出工作目录。
从源码看,差异判定逻辑在 HeapAnalyzer.ts 的diffSnapshots()中:先收集 target 快照之前出现的所有节点 id(baselineIds),target 中新分配的对象进入候选集targetAllocatedHeapNodeIdSet;随后在 final 快照中检查这些对象哪些仍然存活——这些存活对象就是“目标交互分配后未释放”的对象,即本教程所说的泄漏候选。
三、核心操作:--trace-all-objects 把每个对象都当作泄漏
在默认检测器漏报的情况下,运行:
memlab find-leaks --trace-all-objects这条命令指示 memlab 把目标交互分配出的每个对象都视为内存泄漏。此时很可能存在大量在目标交互之后未释放的对象。
参数在源码中的实现见 TraceAllObjectsOption.ts,parse()做了三件事:
- 置
config.oversizeObjectAsLeak = true,从而激活FilterOverSizedNodeAsLeak规则(规则实现见 FilterOverSizedNodeAsLeak.rule.ts); - 若未同时指定
--trace-object-size-above,则把config.oversizeThreshold设为0,即任何 retainedSize 大于 0 的存活新对象都算泄漏; - 设置
config.traceAllObjectsMode。该选项支持--trace-all-objects=default(对象图全量参与)和--trace-all-objects=selected-js-objects(收窄到 JS 对象并过滤 React 内部节点、原型链等噪声节点)两种取值模式;不带等号的布尔形式等价于default。
memlab 随后会对泄漏对象的 retainer trace 做聚类,并按泄漏簇的聚合 retained size降序打印(聚类与排序逻辑在 TraceBucket.ts 及clusterPaths()中)。典型输出如下(原文档示例):
MemLab found 46 leak(s) --Similar leaks in this run: 4-- --Retained size of leaked objects: 8.3MB-- [Window] (native) @35847 [8.3MB] --20 (element)---> [InternalNode] (native) @130981728 [8.3MB] --8 (element)---> [InternalNode] (native) @130980288 [8.3MB] --1 (element)---> [EventListener] (native) @131009888 [8.3MB] --1 (element)---> [V8EventListener] (native) @224808192 [8.3MB] --1 (element)---> [eventHandler] (closure) @168079 [8.3MB] --context (internal)---> [<function scope>] (object) @181905 [8.3MB] --bigArray (variable)---> [Array] (object) @182925 [8.3MB] --elements (internal)---> [(object elements)] (array) @182929 [8.3MB] --Similar leaks in this run: 79-- --Retained size of leaked objects: 16.9KB-- [Window] (native) @35847 [8.3MB] --17 (element)---> [InternalNode] (native) @224820352 [0 byte] --3 (element)---> [InternalNode] (native) @224766112 [0 byte] --1 (element)---> [InternalNode] (native) @224771072 [0 byte] --1 (element)---> [InternalNode] (native) @224723840 [540 bytes] --1 (element)---> [InternalNode] (native) @224723680 [540 bytes] --1 (element)---> [InternalNode] (native) @224818752 [84 bytes] --Similar leaks in this run: 62-- --Retained size of leaked objects: 12.8KB-- [Window] (native) @35847 [8.3MB] --4 (element)---> [HTMLDocument] (native) @35845 [6KB] --part of key -> value pair in ephemeron table (internal)---> [HTMLDocument] (object) @167199 [28 bytes] --__proto__ (property)---> [HTMLDocument] (object) @173029 [144 bytes] --properties (internal)---> [(object properties)] (array) @182697 [76 bytes] ...如何读这份结果:
- 每个“泄漏簇”以
--Similar leaks in this run: N--开头,表示该次运行中有 N 个对象的 retainer trace 模式相似;--Retained size of leaked objects: 8.3MB--是簇内对象的聚合 retained size,簇与簇之间按此值降序排列,因此排在最前面的簇通常是最值得优先排查的; - 每条 trace 是从GC root 到泄漏对象的引用链。上例中链路为:
Window持有 20 个 DOM 元素 → 元素的eventHandler闭包 → 闭包 context 中的局部变量bigArray→ 大数组的elements。要释放bigArray,就要找到链中“本应置空却没有置空”的那一环(比如页面回退后未移除的事件处理器); - 要获得可读的 trace,被测站点需提供非压缩代码(或变量/函数/属性名可读的压缩代码),否则节点名会是混淆后的名字;
- 若终端 trace 难以定位,可以把 memlab 保存的堆快照(位于
$(memlab get-default-work-dir)/data/cur)加载进 Chrome DevTools,直接搜索泄漏对象 id(如@182929)继续排查。
四、降低输出噪声的三种方式
--trace-all-objects会把所有对象都当泄漏,输出往往非常冗长。有几种互补的降噪手段:
1. --trace-object-size-above:按单对象 retainedSize 过滤
只显示retainedSize大于阈值(单位:字节)的 trace,例如大于 1MB:
memlab find-leaks --trace-object-size-above 1000000结果只剩大对象泄漏簇:
MemLab found 1 leak(s) --Similar leaks in this run: 4-- --Retained size of leaked objects: 8.3MB-- [Window] (native) @33651 [8.3MB] --20 (element)---> [InternalNode] (native) @216691968 [8.3MB] --8 (element)---> [InternalNode] (native) @216691168 [8.3MB] --1 (element)---> [EventListener] (native) @216563936 [8.3MB] --1 (element)---> [V8EventListener] (native) @216563776 [8.3MB] --1 (element)---> [eventHandler] (closure) @160711 [8.3MB] --context (internal)---> [<function scope>] (object) @176463 [8.3MB] --bigArray (variable)---> [Array] (object) @176465 [8.3MB] --elements (internal)---> [(object elements)] (array) @176489 [8.3MB]该参数的源码实现在 OversizeThresholdOption.ts:解析后同时置config.oversizeObjectAsLeak = true和config.oversizeThreshold = 阈值,由FilterOverSizedNodeAsLeak规则判定node.retainedSize > config.oversizeThreshold的对象才算泄漏。注意--trace-all-objects与它并不互斥:前者把阈值强制为 0,若同时指定--trace-object-size-above,则以后者设置的阈值为准。
2. --ignore-leak-cluster-size-below:按泄漏簇聚合 retainedSize 过滤
该参数忽略**聚合 retained size 小于阈值(字节)**的泄漏簇,适合关注“小对象但成百上千个”的场景(上例中 16.9KB、12.8KB 的簇就会被过滤掉):
memlab find-leaks --ignore-leak-cluster-size-below 1000000其实现见 LeakClusterSizeThresholdOption.ts,写入config.clusterRetainedSizeThreshold;过滤动作发生在 TraceBucket.ts 的filterClusters()中,在聚类完成、按簇 retained size 降序排列之后执行。
与--trace-object-size-above的区别在于过滤粒度:前者在单对象层面(进入 trace 之前),后者在聚类结果层面(trace 聚类之后),二者可组合使用。
3. 自定义 leak filter
如果泄漏有明确特征(如特定类名、属性组合),更精准的做法是在场景文件中提供一个 leak filter(leakFilter回调)或加载外部过滤器文件(对应--leak-filter-file选项,见FindLeaks的选项列表),让leakFilter对每个目标交互分配且未释放的堆对象(node)返回true/false:
function leakFilter(node, heap) { // ... your leak detector logic // return true to mark the node as a memory leak }自定义过滤器通过FilterByExternalFilter规则接入规则链,优先级仅次于用户heapTag标记,且可以访问完整堆图heap做复杂判定,比单纯的尺寸阈值更精确。
如需查看find-leaks的完整参数说明,随时运行:
memlab find-leaks -h五、参数选择速查
| 目标 | 命令 | 过滤粒度 |
|---|---|---|
| 默认检测器漏报,想看全部未释放对象 | memlab find-leaks --trace-all-objects | 对象级(阈值 0) |
| 只看单对象 retainedSize 超阈值的泄漏 | memlab find-leaks --trace-object-size-above 1000000 | 对象级 |
| 忽略聚合 retainedSize 过小的泄漏簇 | memlab find-leaks --ignore-leak-cluster-size-below 1000000 | 簇级 |
| 泄漏特征明确,按业务规则过滤 | 场景文件leakFilter回调 | 对象级(可编程) |
| 只关注 JS 对象并过滤 React 内部噪声 | memlab find-leaks --trace-all-objects=selected-js-objects | 对象级 |
找到可疑簇之后,还可以继续深入:用memlab analyze unbound-object查看运行中持续增长的未绑定对象、用memlab trace --node-id @<HEAP_OBJECT_ID>单独查看某个对象的 retainer trace,或加载堆快照做交互式分析——这些能力构成 memlab 从“发现”到“定位”到“修复验证”的完整闭环。
参考
- 官方教程:website/docs/guides/03-find-leak.md
- CLI 命令实现:packages/cli/src/commands/heap/CheckLeakCommand.ts
- 选项实现:TraceAllObjectsOption.ts、OversizeThresholdOption.ts、LeakClusterSizeThresholdOption.ts
- 泄漏判定规则链:LeakObjectFilter.ts、LeakFilterRuleList.ts、FilterOverSizedNodeAsLeak.rule.ts
- 快照差异与聚类:HeapAnalyzer.ts、TraceBucket.ts
- 自定义过滤器接口文档:website/docs/api/core/src/interfaces/ILeakFilter.md
- 开发工具
- 测试
- CLI
- MCP 服务
【免费下载链接】memlab
A framework for finding JavaScript memory leaks and analyzing heap snapshots
相关推荐
数学证明也能被计算机验证?mathlib4带你体验形式化数学的魅力
数学证明也能被计算机验证?mathlib4带你体验形式化数学的魅力 你是否曾担心自己的数学证明有逻辑漏洞?或者好奇计算机能否真正理解复杂的数学定理?mathli
形式化验证科研Babylon.js 内存泄漏 CI 测试实战:基于 memlab 的场景化内存泄漏运行器
Babylon.js 内存泄漏 CI 测试实战:基于 memlab 的场景化内存泄漏运行器 本文以 Babylon.js 仓库内 packages/tools/
图形学游戏开发3D渲染memlab SnapshotResultReader 实战:用外部堆快照驱动 findLeaks 内存泄漏检测
memlab SnapshotResultReader 实战:用外部堆快照驱动 findLeaks 内存泄漏检测 SnapshotResultReader 是
开发工具测试CLIMCP 服务
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考