Memlab
是一款 E2E
測試和分析框架,用於發現 JavaScript
記憶體漏失和優化機會。
Memlab
是 JavaScript
的記憶體測試框架。它支援定義一個測試場景(使用 Puppeteer API
),教 Memlab
如何與您的單頁應用程式(SPA
)互動,Memlab
可以自動處理其餘的記憶體漏失檢查:
JavaScript
堆快照npm install -g memlab
memlab help
使用 Memlab
檢測分離的 DOM
元素的教學。Demo 原始碼:
當您單擊 「Create detached DOMs」
按鈕時,Demo app 會洩漏分離的 DOM
元素。每次單擊都會建立 1024
個分離的 DOM
元素,這些元素由 window
物件參照。
// @nolint
import Link from 'next/link';
import React from 'react';
export default function DetachedDom() {
const addNewItem = () => {
if (!window.leakedObjects) {
window.leakedObjects = [];
}
for (let i = 0; i < 1024; i++) {
window.leakedObjects.push(document.createElement('div'));
}
console.log('Detached DOMs are created. Please check Memory tab in devtools')
};
return (
<div className="container">
<div className="row">
<Link href="/">Go back</Link>
</div>
<br />
<div className="row">
<button type="button" className="btn" onClick={addNewItem}>
Create detached DOMs
</button>
</div>
</div>
);
}
原始檔:packages/e2e/static/example/pages/examples/detached-dom.jsx
要在本地機器上執行 demo
,請克隆 memlab
github 儲存庫:
git clone [email protected]:facebookincubator/memlab.git
從 Memlab 專案的根目錄執行以下命令:
cd packages/e2e/static/example
npm install
npm run dev
這將啟動一個範例 Nextjs
app。讓我們通過從瀏覽器存取 http://localhost:3000 來確保它正在執行:
這裡測試的是 Example 1
。
// @nolint
// memlab/packages/e2e/static/example/scenario/detached-dom.js
/**
* 我們要執行的場景的初始 `url`。
*/
function url() {
return "http://localhost:3000/examples/detached-dom";
}
/**
* 指定 memlab 應如何執行您要測試該 action 是否導致記憶體漏失的 action。
*
* @param page - Puppeteer's page object:
* https://pptr.dev/api/puppeteer.page/
*/
async function action(page) {
const elements = await page.$x(
"//button[contains(., 'Create detached DOMs')]"
);
const [button] = elements;
if (button) {
await button.click();
}
// 從 memlab 清理外部參照
await Promise.all(elements.map(e => e.dispose()));
}
/**
* 指定 memlab 應如何執行將重置您在上面執行的 action 的 action。
*
* @param page - Puppeteer's page object:
* 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
。
這可能需要幾分鐘:
cd packages/e2e/static/example
npm run dev # 注意啟動 Demo
memlab run --scenario scenarios/detached-dom.js
對於每個洩漏的物件組,memLab 列印一個具有代表性的洩漏跟蹤。
讓我們從上到下分解結果:
第 1 部分:瀏覽器互動麵包屑顯示了按照我們的場景檔案中指定的方式執行的瀏覽器互動(導航)memlab
。
page-load[6.5MB](baseline)[s1]
- 初始頁面載入時 JavaScript 堆大小為 6.5MB
。baseline
堆快照將在磁碟上儲存為 s1.heapsnapshot
。action-on-page[6.6MB](baseline)[s2]
- 單擊 「Create detached DOMs」
按鈕後,堆大小增加到 6.6MB
。revert[7MB](final)[s3]
- 在離開觸發記憶體漏失的頁面後,該網頁最終達到了 7MB。第 2 部分:洩漏跟蹤的總體摘要
1024 leaks
- 有 1024 個洩漏的物件。example app
的第 12 行在 for 迴圈中建立了 1024 個分離的 DOM 物件。Retained size
- 洩漏物件叢集的聚合保留大小為 143.3KB(記憶體漏失根據保留跟蹤的相似性分組在一起)。第 3 部分:每個洩漏簇的詳細代表洩漏跟蹤
洩漏跟蹤是從 GC 根(垃圾收集器遍歷堆的堆圖中的入口物件)到洩漏物件的物件參照鏈。跟蹤顯示洩漏的物件為何以及如何在記憶體中仍然保持活動狀態。 打破參照鏈意味著洩漏的物件將不再可以從 GC 根存取,因此可以進行垃圾回收。
通過從原生 Window(即 GC 根)向下逐個跟蹤洩漏跟蹤,您將能夠找到應該設定為 null 的參照(但這不是由於bug 引起的)。
map
- 這是正在存取的物件的 V8 HiddenClass
(V8 在內部使用它來儲存有關物件形狀的元資訊和對其原型的參照 - 在此處檢視更多資訊)- 在大多數情況下,這是 V8 實現細節,可以忽略。
prototype
- 這是 Window
類的範例。leakedObjects
- 這表明 leakedObjects
是 Window
物件的一個屬性,大小為 148.5KB
,指向一個 Array
物件。0
- 這表明分離的 HTMLDIVElement
(即當前未連線到 DOM
樹的 DOM
元素)被儲存為leakedObjects
陣列的第一個元素(由於顯示所有 1024
條洩漏痕跡是壓倒性的,Memlab
只列印一個具有代表性的洩漏痕跡。即屬性 0
而不是屬性 0->1023
)簡而言之,從 Window 物件到洩漏物件的洩漏跟蹤路徑為:
[window](object) -> leakedObjects(property) -> [Array](object)
-> 0(element) -> [Detached HTMLDIVElement](native)
與範例中的洩漏程式碼匹配:
window.leakedObjects = [];
for (let i = 0; i < 1024; i++) {
window.leakedObjects.push(document.createElement('div'));
}