内存泄漏检测需执行“触发→清理→稳定→拍摄”闭环:先手动gc并确保空闲,拍基准快照;再启动流、接收数据、主动关闭、清缓存、二次gc后拍对比快照,通过comparison视图分析closure、detached dom、arraybuffer等delta异常。

抓取流处理前后的堆快照做对比,核心是让内存状态“静下来再拍”,避免干扰噪声。重点不在快照数量,而在操作闭环是否完整:触发 → 清理 → 稳定 → 拍摄。
准备阶段:清空干扰,进入稳定空闲态
流处理(如 WebSocket 消息接收、EventSource 推送、可读流 pipeTo)常伴随持续回调和闭包引用,若直接拍快照,大量待回收对象会混入结果。务必先手动触发垃圾回收:
- 打开 Chrome DevTools(F12),切换到 Memory 面板
- 点击左上角小垃圾桶图标 Collect garbage,等几秒直到控制台无响应提示消失
- 确认页面无后台轮询、无未关闭的流连接、无 pending 的 Promise —— 可在 Console 执行
performance.memory观察usedJSHeapSize是否平稳
拍摄基准快照(Snapshot 1)
在流尚未启动、监听器未注册、数据未流入时立即拍摄:
- 确保流相关逻辑(如
new ReadableStream()、source.addEventListener('message'))尚未执行 - 点击 Capture 按钮,保存为 Snapshot 1
- 快照生成后,不要刷新或跳转,保持当前 tab 活跃
执行流处理并完成清理
这不是简单“打开流→收几条消息”,而是模拟真实生命周期闭环:
- 启动流(例如调用
connect()、startStreaming()),接收 3–5 条典型数据 - 主动终止流:调用
controller.close()、source.close()或移除事件监听器(removeEventListener必须与addEventListener使用同一函数引用) - 清空所有缓存变量:如设
this.buffer = null、window.pendingHandlers = new Map(),避免闭包隐式持有 - 再次点击 Collect garbage,等待 JS 堆回落稳定
拍摄对比快照(Snapshot 2)并分析
此时内存应基本回归初始水平;若未回落,泄漏嫌疑明确:
- 点击 Capture,保存为 Snapshot 2
- 在快照列表中选中 Snapshot 2,右上角切换至 Comparison 视图
- Base snapshot 选择 Snapshot 1
- 按 #Delta 降序排列,重点关注:
-
Closure:数量增加且 Retained Size 单个超 100KB,点开看 [[Scopes]] 是否捕获了ReadableStream或MessageEvent实例 -
Detached HTMLDivElement(或类似):说明流渲染的 DOM 节点被闭包/全局 Map 持有未释放 -
ArrayBuffer、Uint8Array:流数据未及时transfer或slice后释放引用
-











