memlab CollectionsHoldingStaleAnalysis 详解:定位 Map/Set/Array 中滞留 Detached 对象的内存泄漏源
开发工具测试CLIMCP 服务【免费下载链接】memlabA framework for finding JavaScript memory leaks and analyzing heap snapshots项目地址https://gitcode.com/gh_mirrors/me/memlab点击查看免费下载本篇围绕 memlab 内置堆分析插件CollectionsHoldingStaleAnalysisCLI 命令名collections-with-stale展开它用于在单个堆快照中找出仍持有“陈旧对象”detached DOM 节点、未卸载的 React Fiber 节点的Map、Set、Array集合。读完本文你可以直接用memlab analyze collections-with-stale或memlab/heap-analysis的编程 API 定位“集合把已卸载/已移除对象一直拽在堆里”这类典型泄漏并理解其底层扫描逻辑、stale 判定规则与输出格式。什么是“持有陈旧对象的集合”前端应用中很常见的泄漏模式是组件卸载或 DOM 节点被移除后本应可被垃圾回收的对象仍被某个长生命周期的集合缓存、监听器注册表、索引表等强引用着。这类集合里的元素可以被称为“陈旧对象”stale objects。在堆快照的视角下这些陈旧对象有两类典型特征Detached DOM 节点已从文档树移除、但仍未被回收的 DOM 元素例如Detached HTMLDivElement未卸载的 Fiber 节点Detached FiberNodeReact 组件卸载后残留在堆中的 Fiber 对象。CollectionsHoldingStaleAnalysis就是专门针对这两种情况做的单快照分析它扫描堆中的Map、Set、Array统计每个集合里有多少直接子元素处于 detached 状态并按陈旧元素占用的 retained size 排序输出帮助开发者快速锁定“哪个集合、多大、里面有哪些陈旧元素”。类结构与继承关系根据官方 API 文档 CollectionsHoldingStaleAnalysis该类的定义与成员如下成员说明构造函数new CollectionsHoldingStaleAnalysis()无参数继承自BaseAnalysis进而继承内部Analysis抽象类getCommandName()覆写基类方法返回 CLI 命令名collections-with-stale用于memlab analyze ANALYSIS_NAME引用analyzeSnapshotFromFile(file, options?)继承自基类对单个.heapsnapshot文件运行分析返回PromiseAnalyzeSnapshotResult源码位于 CollectionsHoldingStaleAnalysis.ts并经由 heap-analysis 包入口 导出// packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts export default class CollectionsHoldingStaleAnalysis extends BaseAnalysis { public getCommandName(): string { return collections-with-stale; } /** internal */ public getDescription(): string { return Analyze collections holding stale objects; } // ... }getCommandName()的返回值同时决定了 CLI 侧的调用方式getDescription()则会显示在memlab analyze list的输出中。CLI 使用方式在 memlab 命令行工具中该分析对应memlab analyze的collections-with-stale子命令见 CLI-commands.mdmemlab analyze collections-with-stale它接受的插件级参数有两个源码中由getOptions()返回SnapshotFileOption与OutputOption参数说明--snapshot指定待分析堆快照文件的路径.heapsnapshot--output分析结果输出格式可选text/json默认text此外还支持memlab analyze的全局选项例如--work-dir设置本次分析的工作目录、--verbose显示更多细节、--silent静音终端输出等。一个典型用法是# 对指定快照运行集合陈旧对象分析输出 JSON 便于脚本处理 memlab analyze collections-with-stale \ --snapshot./memlab-logs/tab-0/2026-10-12-14-00-00.heapsnapshot \ --outputjson适用前提该分析面向单个堆快照。它不支持目录级多快照分析——在 CollectionsHoldingStaleAnalysis.ts 中analyzeSnapshotsInDirectory()被显式覆写为抛出异常CollectionsHoldingStaleAnalysis does not support analyzeSnapshotsInDirectory。如果需要观察对象随时间增长的趋势应改用unbound-collection、unbound-object等多快照分析插件。编程 APIanalyzeSnapshotFromFile()按照 API 文档可以通过继承自BaseAnalysis的analyzeSnapshotFromFile()以编程方式运行分析import {CollectionsHoldingStaleAnalysis} from memlab/heap-analysis; const analysis new CollectionsHoldingStaleAnalysis(); // 分析控制台的输出被保存到 result.analysisOutputFile const result await analysis.analyzeSnapshotFromFile(snapshotFile);参数与返回值定义见 BaseAnalysis.tsfile: string——.heapsnapshot文件的绝对路径options?: RunHeapAnalysisOptions—— 可选的运行配置当前仅支持workDir指定后分析过程的中间文件、日志文件与最终输出都会写入该目录目录不存在时 memlab 会自动创建返回值PromiseAnalyzeSnapshotResult—— 其中analysisOutputFile字段是本次分析控制台输出的日志文件路径类型定义见 PluginUtils.ts。指定工作目录的完整示例const analysis new CollectionsHoldingStaleAnalysis(); const result await analysis.analyzeSnapshotFromFile(snapshotFile, { // 若目录不存在memlab 会自动创建 workDir: /tmp/your/work/dir, });内部实现上analyzeSnapshotFromFile()会先注册一个日志文件然后以snapshot: file的伪 CLI 参数形式调用插件的process()最后把日志文件路径作为AnalyzeSnapshotResult返回。分析结果本身各集合的陈旧元素列表则以终端文本或 JSON 形式写入该日志文件。源码级剖析扫描与 stale 判定逻辑只关注三类集合Map、Set、Array分析的核心在process()回调memlab analyze collections-with-stale的执行入口见 源码先通过pluginUtils.loadHeapSnapshot(options)加载并解析堆快照同时计算 dominator 树与 retained size再调用getCollectionsWithStaleValues()收集结果最后按--output配置选择print()文本或printJson()JSON输出。哪些节点被视为“集合”由实例成员staleCollectionMapper决定// packages/heap-analysis/src/plugins/CollectionsHoldingStaleAnalysis.ts private staleCollectionMapper: Mapstring, (node: IHeapNode) CollectionStat new Map([ [Map, processMapOrSet], [Set, processMapOrSet], [Array, processCollectionChildren], ]);getCollectionsWithStaleValues()遍历快照中所有可追踪节点snapshot.nodes.forEachTraceable仅对名称命中Map/Set/Array的节点收集统计统计中没有任何陈旧子元素的集合会被直接丢弃staleChildren.length 0才入列。从源码结构看这一设计是刻意为之WeakMap/WeakSet持有弱引用不构成强引用泄漏因此不在分析范围内。Map/Set 需要透过 table 边找到真正的元素Map与Set在 V8 堆快照中有一层间接结构集合对象本身通过一条名为table的边指向内部的哈希表对象元素是挂在该表对象上的。processMapOrSet()正是利用了这一点function processMapOrSet(node: IHeapNode): CollectionStat { // 取集合对象的 table 出边指向内部哈希表 const tableEdge utils.getEdgeByNameAndType(node, table); if (!tableEdge) { return initCollectionStat(node); } return processCollectionChildren(tableEdge.toNode); }而Array的元素直接通过下标边挂在数组节点上所以直接交给processCollectionChildren()。getEdgeByNameAndType的实现见 Utils.ts。每个集合的统计项CollectionStat对每个命中的集合分析会构建一个CollectionStat统计结构type CollectionStat { collection: IHeapNode; // 集合节点本身 staleChildren: IHeapNode[]; // 处于 stale 状态的直接子元素 childrenSize: number; // 集合的直接子元素总数 staleRetainedSize: number; // stale 子元素的 retainedSize 累加值 };processCollectionChildren()的逻辑是逐条遍历集合节点的出边node.references对每个目标元素节点做 stale 判定并累计function processCollectionChildren(node: IHeapNode): CollectionStat { const stat initCollectionStat(node); const refs node.references; for (const ref of refs) { stat.childrenSize; const elementNode ref.toNode; if ( !utils.isDetachedDOMNode(elementNode) !utils.isDetachedFiberNode(elementNode) ) { continue; } stat.staleChildren.push(elementNode); stat.staleRetainedSize elementNode.retainedSize; } return stat; }这里的关键是只检查集合的直接子元素而不是集合可支配dominated的整个子树——这与“集合直接持有元素”的语义一致也避免把嵌套在普通对象内部的 detached 节点误计入。stale 判定isDetachedDOMNode 与 isDetachedFiberNode两个判定函数都来自memlab/core的 utils。其共同基础是isDetached()// packages/core/src/lib/Utils.ts function isDetached(node: IHeapNode): boolean { if (config.snapshotHasDetachedness) { return node.is_detached; // 快照带 detachedness 元数据时直接读字段 } return node.name.startsWith(Detached ); // 否则退回按节点名称前缀判断 }isDetachedFiberNode(node)node是FiberNode/Detached FiberNode且满足isDetachedisDetachedDOMNode(node)排除 Fiber 节点后判断节点是否处于 detached 状态例如Detached HTMLDivElement、Detached InternalNode等已从文档树摘除的 DOM 节点。需要留意的适用边界源码注释明确指出当快照未携带 detachedness 字段时判定依赖Detached名称前缀这类 FiberNode 若无 detachedness 字段则无法被识别isDetachedFiberNode上方的 NOTE 注释。输出格式文本与 JSON结果统一按staleRetainedSize降序排序且只输出前MAX_COLLECTION_STAT_ITEMS 20个集合常量定义在 源码第 21 行。文本模式默认print()若没有任何命中集合输出Congratulations! No collections holding stale objects否则逐条打印Collections holding stale objects: · Array (12345) 2.4 MB (3 out of 102 elements are stale): 12900, 12901, 13055, ...每条包含集合类型名、节点 ID、集合的 retained size经utils.getReadableBytes人类可读化以及“N out of M elements are stale”的陈旧比例描述陈旧子元素 ID 最多打印 10 个超出部分以...结尾见 print 实现。JSON 模式--outputjsonprintJson()输出一个 JSON 数组每项字段为字段含义id集合节点 IDsize集合 retained size可读字节childrenSize集合直接子元素总数staleChildrenSize其中 stale 的元素数量staleChildrenIds全部 stale 元素节点 ID 列表JSON 模式适合接入脚本或 Agent 做二次处理例如批量提取staleChildrenIds后逐一追踪引用链。从分析结果到定位泄漏源collections-with-stale给出的节点 ID 可以直接衔接 memlab 的其它能力形成一条完整的排查链路用memlab trace --node-idHEAP_OBJECT_ID追踪某个 stale 元素或集合本身的 retainer 链看它被哪条路径钉在堆上结合memlab analyze detached-DOM、unmounted-fiber-node等单快照分析交叉验证如果怀疑集合本身在持续膨胀条目数单调增长再切换到多快照的memlab analyze unbound-collection用--snapshot-dir观察一段时间序列。小结CollectionsHoldingStaleAnalysis是 memlab 中一个目标明确的单快照堆分析插件它仅针对Map、Set、Array三类集合通过table边穿透 Map/Set 的内部结构用isDetachedDOMNode/isDetachedFiberNode判定直接子元素是否“陈旧”最终按陈旧元素 retained size 输出 Top 20 命中集合。无论是通过 CLImemlab analyze collections-with-stale --snapshot... [--outputjson]还是编程 APIanalyzeSnapshotFromFile(file, {workDir})调用都能为“集合滞留 detached 对象”这一高频泄漏模式提供直接的量化线索。核心实现可进一步参阅 CollectionsHoldingStaleAnalysis.ts、BaseAnalysis.ts 与 core 的 utils。赞分享开发工具测试CLIMCP 服务【免费下载链接】memlabA framework for finding JavaScript memory leaks and analyzing heap snapshots项目地址https://gitcode.com/gh_mirrors/me/memlab点击查看免费下载相关推荐memlab 的 findLeaks() API从堆快照中定位 JavaScript 内存泄漏memlab 的 findLeaks API从堆快照中定位 JavaScript 内存泄漏 findLeaks 是 memlab/api https://l开发工具测试CLIMCP 服务给AI Agent一个浏览器Obscura MCP服务器完整配置指南Claude Desktop/Cursor实战给AI Agent一个浏览器Obscura MCP服务器完整配置指南Claude Desktop/Cursor实战 Obscura 是一个用 Rust 编网页爬虫后端MCP 服务浏览器控制memlab API 详解使用 findLeaksBySnapshotFilePaths() 基于已有堆快照文件定位 JavaScript 内存泄漏memlab API 详解使用 findLeaksBySnapshotFilePaths 基于已有堆快照文件定位 JavaScript 内存泄漏 导读 fin开发工具测试CLIMCP 服务上一篇Alpine Linux 运维速查手册apk 包管理、OpenRC 服务与容器化实践下一篇Manim 动画中的 Updaters 与 ValueTracker构建逐帧响应的连续动画manim-video 实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考