当前位置:首页 > 技术 > 正文内容

Memlab:用于检测浏览器和 Node.js 内存泄漏的 JavaScript 堆分析工具

访客 技术 2026年6月24日 1

image

Memlab 是 Meta 推出的端到端测试与分析框架,专门用于识别 JavaScript 内存泄漏并发现优化机会。

该工具提供完整的工作流程来自动化内存泄漏检测:模拟用户与单页应用的交互、获取 JavaScript 堆快照、对快照进行深度分析、过滤无关数据、聚合相似的泄漏模式,并生成详细的保留器追踪路径供调试使用。

安装配置

通过 npm 全局安装 Memlab:

npm install -g memlab
memlab help

示例应用:检测分离 DOM 元素

本教程演示如何利用 Memlab 检测被分离的 DOM 元素导致的内存泄漏。示例应用源代码位于官方仓库:

准备演示应用

点击"Create detached DOMs"按钮会创建 1024 个分离的 DOM 元素,这些元素被 window 对象引用从而无法被垃圾回收。

image

// @nolint

import Link from 'next/link';
import React from 'react';

export default function DetachedDom() {

  const handleCreateElements = () => {
    if (!window.cachedElements) {
      window.cachedElements = [];
    }
    for (let i = 0; i < 1024; i++) {
      const element = document.createElement('div');
      window.cachedElements.push(element);
    }
    console.log('分离的 DOM 元素已创建,请查看开发者工具 Memory 面板');
  };

  return (
    <div className="container">
      <div className="row">
        <Link href="/">返回</Link>
      </div>
      <br />
      <div className="row">
        <button type="button" className="btn" onClick={handleCreateElements}>
          创建分离 DOM 元素
        </button>
      </div>
    </div>
  );
}

源文件:packages/e2e/static/example/pages/examples/detached-dom.jsx

步骤一:克隆项目仓库

将 memlab 仓库克隆到本地:

git clone git@github.com:facebookincubator/memlab.git

步骤二:启动示例应用

在项目根目录执行以下命令启动 Next.js 示例应用:

cd packages/e2e/static/example
npm install
npm run dev

确保应用已在 http://localhost:3000 运行,然后选择 "Example 1" 进行测试。

image

执行内存泄漏检测

创建场景定义文件

// @nolint
// memlab/packages/e2e/static/example/scenario/detached-dom.js
/**
 * 定义测试场景的入口 URL。
 */
function url() {
  return "http://localhost:3000/examples/detached-dom";
}

/**
 * 定义执行测试操作的交互逻辑。
 * 此操作用于验证是否存在内存泄漏。
 *
 * @param page - Puppeteer 页面对象:
 * https://pptr.dev/api/puppeteer.page/
 */
async function action(page) {
  const targetButton = await page.$x(
    "//button[contains(., '创建分离 DOM 元素')]"
  );
  const [button] = targetButton;
  if (button) {
    await button.click();
  }
  // 清理外部引用资源
  await Promise.all(targetButton.map(e => e.dispose()));
}

/**
 * 定义还原操作的交互逻辑。
 * 用于恢复执行 action 前的页面状态。
 *
 * @param page - Puppeteer 页面对象:
 * https://pptr.dev/api/puppeteer.page/
 */
async function back(page) {
  await page.click('a[href="/"]');
}

module.exports = { action, back, url };

场景文件保存路径:packages/e2e/static/example/scenario/detached-dom.js

执行 Memlab 分析

运行以下命令开始分析(首次运行可能需要几分钟):

cd packages/e2e/static/example
npm run dev # 确保示例应用正在运行
memlab run --scenario scenarios/detached-dom.js

image

分析泄漏追踪结果

Memlab 会为每组泄漏对象输出具有代表性的追踪信息。

image

第一部分:浏览器交互记录

展示按照场景配置执行的浏览器操作序列:

  • page-load[6.5MB](baseline)[s1] - 初始页面加载时的 JavaScript 堆大小,baseline 堆快照保存为 s1.heapsnapshot
  • action-on-page[6.6MB](baseline)[s2] - 点击"创建分离 DOM 元素"按钮后的堆大小
  • revert[7MB](final)[s3] - 离开触发泄漏的页面后的最终堆大小

第二部分:泄漏摘要统计

  • 1024 leaks - 共检测到 1024 个泄漏对象,示例应用在 for 循环中创建了 1024 个分离 DOM 元素
  • Retained size - 泄漏对象集群的聚合保留大小为 143.3KB(内存泄漏按保留跟踪相似性分组)

第三部分:详细泄漏追踪

泄漏追踪是从 GC 根(垃圾回收器遍历堆图时的入口对象)到泄漏对象的对象引用链。追踪揭示泄漏对象为何及如何保持在内存中。打破引用链可使泄漏对象被垃圾回收。

通过从原生 Window 对象(GC 根)向下追踪泄漏路径,可以定位到需要置空的引用。

  • map - 对象的 V8 HiddenClass(V8 内部用于存储对象形状元信息和原型引用,可忽略此实现细节)
  • https://v8.dev/blog/fast-properties#hiddenclasses-and-descriptorarrays
  • prototype - Window 类的实例
  • cachedElements - Window 对象的属性,保留大小 148.5KB,指向 Array 对象
  • 0 - 分离的 HTMLDIVElement(未连接到 DOM 树的 DOM 元素)存储在数组的第一个位置(由于打印全部 1024 条追踪信息过于冗长,Memlab 仅输出代表性追踪,即属性 0 而非 0->1023)

泄漏路径如下:

[window](object) -> cachedElements(property) -> [Array](object)
  -> 0(element) -> [Detached HTMLDIVElement](native)

与示例代码中的泄漏逻辑对应:

window.cachedElements = [];
for (let i = 0; i < 1024; i++) {
    window.cachedElements.push(document.createElement('div'));
}

相关资源

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。