检测超大对象
本教程演示如何检测 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
通过检查泄漏跟踪,我们可以看到 bigArray
中的 elements
被保留,因为 bigArray
被 eventHandler
闭包保留,而 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
如果你需要更高级的过滤逻辑,这里有更多示例。