跳至主要内容

检测超大对象

本教程演示如何检测 Web 应用程序未释放的超大对象。

我们建议阅读检测演示应用中的内存泄漏,它将引导你了解如何解释 memlab 结果并调试泄漏跟踪。

设置待测示例 Web 应用程序

该演示应用程序在每次 React 渲染调用中泄漏一个大数组(通过未注册的事件处理程序和闭包作用域链)。

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * @nolint * @oncall memory_lab */import Link from 'next/link';import React, {useEffect} from 'react';export default function OversizedObject() {  const bigArray = Array(1024 * 1024 * 2).fill(0);  const eventHandler = () => {    // the eventHandler closure keeps a reference    // to the bigArray in the outter scope    console.log('Using hugeObject', bigArray);  };  useEffect(() => {    // eventHandler is never unregistered    window.addEventListener('custom-click', eventHandler);  }, []);  return (    <div className="container">      <div className="row">        <Link href="/">Go back</Link>      </div>      <br />      <div className="row">        Object<code>bigArray</code>is leaked. Please check <code>Memory</code>{' '}        tab in devtools      </div>    </div>  );}

源文件:packages/e2e/static/example/pages/examples/oversized-object.jsx

1. 克隆存储库

要在本地计算机上运行演示 Web 应用程序,请克隆 memlab github 存储库

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

2. 运行示例应用程序

将存储库克隆到本地计算机后,从 memlab 项目的根目录运行以下命令

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

这将启动一个示例 Nextjs 应用程序。 让我们通过浏览器访问 https://:3000 来确保它正在运行

注意

端口号 :3000 在你的情况下可能不同。

首次尝试查找泄漏

1. 创建场景文件

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * @nolint * @oncall memory_lab */function url() {  return 'https://:3000/';}// action where you suspect the memory leak might be happeningasync function action(page) {  await page.click('a[href="/examples/oversized-object"]');}// how to go back to the state before actionwasync function back(page) {  await page.click('a[href="/"]');}module.exports = {action, back, url};

让我们将此文件另存为 ~/memlab/scenarios/oversized-object.js

2. 运行 memlab

这需要几分钟。

memlab run --scenario ~/memlab/scenarios/oversized-object.js

这次 memlab 没有发现任何内存泄漏。

当前内置的泄漏检测器仅将满足以下所有条件的对象视为内存泄漏

  • 该对象由 action 回调触发的交互分配。
  • 该对象在 back 回调触发的交互后未释放。
  • 该对象是分离的 DOM 元素或未卸载的 React Fiber 节点。

action 回调分配的其他对象可能是 Web 应用程序故意保留的缓存,因此默认情况下 memlab 不会将它们报告为泄漏。

过滤掉大型对象

我们经常发现有用的一个规则是检查具有非平凡大小(例如 1MB)的未释放对象。

memlab 提供了一个额外的 leakFilter 回调来过滤掉具有自定义规则的泄漏对象。

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * @nolint * @oncall memory_lab */function url() {  return 'https://:3000/';}// action where you suspect the memory leak might be happeningasync function action(page) {  await page.click('a[href="/examples/oversized-object"]');}// how to go back to the state before actionwasync function back(page) {  await page.click('a[href="/"]');}// leakFilter is called with each object (node) in browser// allocated by `action` but not released after the `back` callfunction leakFilter(node, _snapshot, _leakedNodeIds) {  return node.retainedSize > 1000 * 1000;}module.exports = {action, back, leakFilter, url};

现在让我们使用更新的场景文件重新运行 memlab

memlab run --scenario ~/memlab/scenarios/oversized-object.js

memlab run result

通过检查泄漏跟踪,我们可以看到 bigArray 中的 elements 被保留,因为 bigArrayeventHandler 闭包保留,而 eventHandler 闭包又被保留,因为它仍然注册为 EventListener 的处理程序。 如果我们像下面演示的那样向我们的 useEffect 添加一个清理函数,然后再次运行该场景,我们将看到 memlab 不再报告任何泄漏。

useEffect(() => {
window.addEventListener('custom-click', eventHandler);
return () => {
// clean up
window.removeEventListener('custom-click', eventHandler);
};
}, []);
注意

另一种方法是创建一个 leak-filter.js 并将其传递给 memlab find-leaks,如果你已经运行了 memlab run

/** * Copyright (c) Meta Platforms, Inc. and affiliates. * * @nolint * @oncall memory_lab */// leakFilter is called with each object (node) in browser// allocated by `action` but not released after the `back` callfunction leakFilter(node, _snapshot, _leakedNodeIds) {  return node.retainedSize > 1000 * 1000;}module.exports = {leakFilter};

分解 leakFilter 函数并将其保存在文件中,例如,~/memlab/leak-filters/leak-filter-by-retained-size.js

memlab find-leaks --leak-filter ~/memlab/leak-filters/leak-filter-by-retained-size.js

如果你需要更高级的过滤逻辑,这里有更多示例