Memlab是什么
Memlab is a memory testing framework for JavaScript。
Analyzes JavaScript heap and finds memory leaks in browser and node.js。
Memlab是一个JavaScript内存测试框架,可用于在浏览器、Node环境中分析JavaScript堆内存并检测内存泄露。它通过自定义测试场景,与SPA应用交互(使用 Puppeteer API
),然后自动完成内存泄漏检查。
它的工作原理如下:
- 与浏览器交互并获取
JavaScript
堆快照 - 分析堆快照并识别内存泄漏
- 对内存泄漏进行聚合、分组
- 生成可Debug的分析结果
Memlab的特点
- 面向对象的堆遍历 API:支持自定义内存泄露检测器,支持基于Chromium内核的应用(浏览器、Node环境、Electron、Hermes)
- Memory CLI 工具箱:内置 CLI 工具箱和 API
- Node环境下支持内存断言:可以对单元测试或运行中的Node应用保存堆快照,执行内存检查和内存断言
如何安装
ruby
$ npm install -g memlab
- 运行环境要求:Node.js 16+
- 需要科学上网:Memlab内部依赖种包含 Puppeteer,正常情况下安装会报错。
- 针对第2点,官方建议设置环境变量
PUPPETEER_SKIP_DOWNLOAD
先忽略浏览器的下载。手动安装好Puppeteer
后,再手动下载Chromium文件,解压并放到Puppeteer
的默认读取目录下。
如何使用
MemLab是在基于Chromium内核的浏览器中,运行预定义的测试场景并对 JavaScript heap snapshots 进行差异分析,从而发现内存泄漏,步骤如下:
- 导航到目标页面并返回
- 查找未释放的对象
- 显示泄露追踪结果
创建一个测试场景
将该测试场景保存为/memlab/scenario.js
javascript
// 测试场景的初始url
function url() {
return "https://www.baidu.com";
}
// 定义可能出现内存泄露的交互
async function action(page) {
await page.click('[id="target"]');
}
// 指定回到交互前状态的方式
async function back(page) {
await page.click('[id="back"]');
}
module.exports = { action, back, url };
运行测试场景
shell
$ memlab run --scenario /memlab/scenario.js
运行结果分析
第一部分,MemLab 会实时生成一个面包屑,显示与目标网页交互的进度,对每个步骤的解读如下:
-
page-load[6.5MB](baseline)[s1]
- 测试试场景起点
- 初始页面加载时,JavaScript 堆内存大小为 6.5MB。
- baseline内存快照将作为 s1.heapsnapshot 保存在磁盘上。
-
action-on-page[6.6MB](target)[s2]
- 执行交互操作
- 内存大小增加到 6.6MB
- target内存快照将作为 s2.heapsnapshot 保存在磁盘上。
-
revert[7MB](final)[s3]
- 执行回退/反向操作
- 网页内存达到7MB,
- final内存快照将作为 s3.heapsnapshot 保存在磁盘上。
第二部分,对检测到的内存泄露进行汇总
- 内存泄露节点数
- 泄露内存占用大小
第三部分 ,按照内存泄露类型的相似性,对每个种类提取一个代表性的内存泄露节点进行展示,图中是创建1024个分离的DOM节点的内存泄露检测结果分析。
ini
window.leakedObjects = [];
for (let i = 0; i < 1024; i++) {
window.leakedObjects.push(document.createElement('div'));)
}
map
这是被访问对象的 V8 HiddenClass(V8 在内部使用它来存储对象结构信息和对其原型的引用) - 在大多数情况下,这是 V8 实现的细节,可以忽略。prototype
windows实例leakedObjects
表明leakedObjects是Window的属性,大小为148.5KB,指向Array对象0
分离的 HTMLDIVElement元素,被存储为 leakedObjects 数组的第一个元素(Memlab 只打印一个具有代表性的内存泄露)
scss
[window](object) -> leakedObjects(property) -> [Array](object)
-> 0(element) -> [Detached HTMLDIVElement](native)
扩展应用
检测未释放的超大对象
scala
// 未清空EventListener,无法释放eventHandler函数,eventHandler函数保存了对bigArray的引用
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class OversizedObject extends Vue {
public bigArray = Array(1024 * 1024 * 2).fill(0);
eventHandler () {
console.log('Using hugeObject', bigArray);
};
mounted () {
window.addEventListener('custom-click', eventHandler)
}
}
上述示例在运行测试场景时,无法检测到内存泄露。因为Memlab的泄漏检测器仅将满足以下所有条件的对象视为内存泄漏:
- 对象在触发action时分配 内存
- 在触发back后,对象 内存 没有被释放
- 该对象是一个分离的 DOM 元素或一个未挂载的 React Fiber 节点
这种情况下,可以用LeakFilter
来自定义规则过滤内存泄露对象(如内存占用大小),LeakFilter
会对每一个由action触发内存分配,但在back后未释放内存的对象进行调用。
java
// ...
function leakFilter(node, _snapshot, _leakedNodeIds) {
return node.retainedSize > 1000 * 1000;
}
module.exports = {action, back, leakFilter, url};
另外也可以创建一个单独的leakFilter.js文件
ini
function leakFilter(node, _snapshot, _leakedNodeIds) {
return node.retainedSize > 1000 * 1000;
}
module.exports = {leakFilter};
css
$ memlab find-leaks --leak-filter /memlab/leak-filter.js
检测所有的内存泄露
默认情况下,Memlab只报告准确度高的内存泄漏(由其内置的内存泄漏检测器进行判断)。可能会存在一些内存泄漏,Memlab不会报告。
使用如下命令可以检测所有的 内存 泄露 (确保测试场景不包含LeakFilter
):
shell
$ memlab run --scenario /memlab/scenarios.js
lua
$ memlab find-leaks --trace-all-objects
直接分析内存快照
通常情况下,Memlab的内存分析数据来源于Memlab对Puppeteer API
的调用。
通过Memlab内置的Memlab API
,可以使用Memlab直接分析基于从Chrome或任何基于Chromium内核的应用中获取的单个JavaScript堆快照,检测内存问题
css
$ memlab view-heap --snapshot <PATH TO .heapsnapshot FILE>
自动化内存泄露检测
Memlab支持自动化内存泄露检测,配置步骤如下:
- 准备覆盖关键交互的测试场景
- 通过
Memlab CLI
或Memlab API
触发测试场景运行 - 收集结果
在CLI中运行
css
$ memlab run --scenario /path/to/test/scenario/file.js \
--work-dir /path/to/save/memlab/run/results/
在Node.js中运行
ini
const {run} = require('@memlab/api');
const scenario = require('/path/to/test/scenario/file.js');
const fs = require('fs-extra');
(async function () {
const workDir = '/path/to/save/memlab/run/results/';
fs.mkdirsSync(workDir);
const result = await run({scenario, workDir});
})();
Memlab 运行完成后,所有结果和数据将保存在指定的工作目录中(workDir ),可以使用内置的结果分析器BrowserInteractionResultReader对结果进行读取并输出。
ini
const {BrowserInteractionResultReader} = require('@memlab/api');
const workDir = '/path/to/save/memlab/run/results/';
const result = BrowserInteractionResultReader.from(workDir);
// 获取内存快照文件
const files = result.getSnapshotFiles();
// 对结果进行打印
const metaInfo = result.getRunMetaInfo();
console.log(metaInfo.browserInfo._consoleMessages.join('\n'));
// 清除结果
result.cleanup();