Meta開(kāi)源JavaScript內(nèi)存泄漏監(jiān)測(cè)工具M(jìn)emLab安裝使用
一、MemLab簡(jiǎn)介
上周,F(xiàn)acebook母公司Meta 宣布了開(kāi)源 MemLab,一個(gè)基于 Chromium 的瀏覽器的 JavaScript 應(yīng)用程序內(nèi)存泄漏監(jiān)測(cè)工具。同時(shí),F(xiàn)acebook 技術(shù)團(tuán)隊(duì)指出:“應(yīng)用程序的性能和功能正確性問(wèn)題通常會(huì)被用戶(hù)立即留意到。然而內(nèi)存泄漏卻不一樣,它不容易被立即察覺(jué),但它每次都會(huì)吃掉一大塊內(nèi)存,使得整個(gè)網(wǎng)絡(luò)會(huì)話(huà)的響應(yīng)變得非常慢。”
為了幫助開(kāi)發(fā)人員解決這個(gè)問(wèn)題,Meta 構(gòu)建了MemLab,它可以自動(dòng)進(jìn)行內(nèi)存泄漏檢測(cè)并更容易找到泄漏的根本原因。據(jù)官方公告稱(chēng),Meta 內(nèi)部使用它成功地控制了不可持續(xù)的內(nèi)存增長(zhǎng),并識(shí)別了產(chǎn)品和基礎(chǔ)設(shè)施中的內(nèi)存泄漏和內(nèi)存優(yōu)化機(jī)會(huì)。目前,Meta 已經(jīng)在 GitHub 上開(kāi)源了 MemLab。
Facebook在 2020 年被重新設(shè)計(jì)為單頁(yè)應(yīng)用程序 (SPA),該應(yīng)用程序的大部分渲染和導(dǎo)航使用客戶(hù)端 JavaScript。而 Meta 的大多數(shù)其他流行網(wǎng)絡(luò)應(yīng)用程序都使用了類(lèi)似的架構(gòu)來(lái)構(gòu)建,包括 Instagram 和 Workplace。
雖然這種架構(gòu)使其能夠提供更快的用戶(hù)交互、更好的開(kāi)發(fā)人員體驗(yàn)和更像應(yīng)用程序的感覺(jué),但在客戶(hù)端維護(hù) Web 應(yīng)用程序狀態(tài)會(huì)使有效管理客戶(hù)端內(nèi)存變得更加復(fù)雜。且內(nèi)存泄漏的后果在單頁(yè)應(yīng)用程序(SPA)中更為嚴(yán)重,因?yàn)橛脩?hù)可能會(huì)在較長(zhǎng)時(shí)間內(nèi)持續(xù)與頁(yè)面交互,而 MemLab 就是專(zhuān)為這種場(chǎng)景設(shè)計(jì)的。
在許多情況下,JavaScript 可能會(huì)泄漏內(nèi)存。比如,F(xiàn)acebook 工程師 Liang Gong 和 Glenn Conner 就在公告中談到,當(dāng)你向 Chrome 控制臺(tái)發(fā)送一個(gè)對(duì)象時(shí),Chrome 會(huì)對(duì)其進(jìn)行隱藏引用,以防止它被收集。另外,auth0 工程師 Sebastian Peyrott 也曾談到,其他可能出現(xiàn)泄漏或未綁定內(nèi)存增長(zhǎng)的情況則與意外使用全局變量、忘記計(jì)時(shí)器或回調(diào)以及 DOM 外引用有關(guān)。
雖然 Chrome 開(kāi)發(fā)者工具提供了檢查 JavaScript 代碼的內(nèi)存行為的基本手段,比如時(shí)間線(xiàn)視圖和配置文件視圖,但這并不直接,也不能自動(dòng)化。相反,MemLab 則可以很容易地集成到 CI/CD 管道中,Gong 和 Conner 介紹道。
二、工作原理
MemLab 的工作原理是通過(guò)預(yù)定義的測(cè)試場(chǎng)景運(yùn)行 headless 瀏覽器并對(duì) JavaScript heap snapshots 進(jìn)行差異分析來(lái)發(fā)現(xiàn)內(nèi)存泄漏。要達(dá)到這一目的,需要經(jīng)過(guò)如下幾步:
- 導(dǎo)航到頁(yè)面并返回;
- 查找未釋放的對(duì)象;
- 顯示泄露追蹤結(jié)果。
據(jù)悉,MemLab 使用了一個(gè)名為“Puppeteer”的 Node.js 庫(kù)。它可以控制 Google Chrome 或其它基于 Chromium 內(nèi)核打造的瀏覽器,且默認(rèn)情況下以 headless 模式運(yùn)行(方便命令行交互)。
Facebook 工程師解釋稱(chēng),MemLab 的工作方式就是導(dǎo)航到一個(gè)頁(yè)面、然后離開(kāi)。正常情況下,可預(yù)計(jì)該頁(yè)面分配的大部分內(nèi)存也將被釋放。但若沒(méi)有被釋放,則意味其存在極高的內(nèi)存泄露可能性。
我們知道,React 使用存儲(chǔ)在樹(shù)結(jié)構(gòu)中、被稱(chēng)作 Fibers 的對(duì)象,來(lái)表示內(nèi)存中的瀏覽器文檔對(duì)象模型(DOM)。據(jù)該團(tuán)隊(duì)所述,這可能是存在“巨大內(nèi)存泄露”的一個(gè)主要原因。擁有強(qiáng)連接圖的缺點(diǎn)很是顯著,若有任何外部引用指向圖的任何部分,就無(wú)法對(duì)整個(gè)圖開(kāi)展垃圾回收。
對(duì)于瀏覽器內(nèi)存泄漏檢測(cè),MemLab 需要開(kāi)發(fā)人員提供的唯一輸入是一個(gè)測(cè)試場(chǎng)景文件,該文件定義了如何通過(guò) overriding Puppeteer API 和 CSS 選擇器的三個(gè)回調(diào)來(lái)與網(wǎng)頁(yè)進(jìn)行交互。MemLab 會(huì)自動(dòng)對(duì) JavaScript heap 進(jìn)行差異化處理,完善內(nèi)存泄漏,并對(duì)結(jié)果進(jìn)行匯總。
MemLab 的另一特性,就是提供了 JavaScript 堆的圖形視圖、啟用了用于檢查堆快照的 API 。這意味著開(kāi)發(fā)者能夠編寫(xiě)開(kāi)展內(nèi)存斷言的測(cè)試,例如聲明某個(gè)對(duì)象將不再存在于內(nèi)存中。
此外還有一個(gè)用于查找重復(fù)字符串實(shí)例的工具,在某個(gè)案例中,團(tuán)隊(duì)發(fā)現(xiàn)字符串占用了 70% 的堆、且其中半數(shù)至少有一個(gè)重復(fù)的實(shí)例。包括 Chrome、Edge、Firefox 在內(nèi)的瀏覽器,都有附帶內(nèi)存檢查工具。但正如以為開(kāi)發(fā)者在 Hacker News 上吐槽的那樣,這些開(kāi)發(fā)工具難以在調(diào)試過(guò)程中揪出內(nèi)存泄露的問(wèn)題。
最后,MemLab 的另一項(xiàng)強(qiáng)大功能,就是可以在測(cè)試期間作為命令過(guò)程的一部分而運(yùn)行。這意味著如果代碼中引入了嚴(yán)重的泄露,開(kāi)發(fā)者們也能夠在投入生產(chǎn)環(huán)境前加以捕獲。
除了內(nèi)存泄漏檢測(cè)之外,MemLab還包括一組用于查找內(nèi)存優(yōu)化機(jī)會(huì)的內(nèi)置CLI命令和api,并提供如下的功能:
- 堆內(nèi)容分解
- 監(jiān)測(cè)單個(gè)對(duì)象的內(nèi)存使用情況
- 查找重復(fù)的字符串實(shí)例
比如,監(jiān)測(cè)瀏覽內(nèi)存泄漏部分UI。
跟蹤UI內(nèi)存泄漏的整個(gè)鏈路。
三、基本使用
3.1 安裝與使用
首先,需要全局安裝MemLab插件,安裝的命令如下:
npm install -g memlab
例如下面是找到谷歌Maps中的內(nèi)存泄漏的例子,我媽可以創(chuàng)建一個(gè)場(chǎng)景文件來(lái)定義如何與谷歌Maps進(jìn)行交互,比如將其命名為test-google-maps.js。
function url() { return 'https://www.google.com/maps/@37.386427,-122.0428214,11z'; } async function action(page) { await page.click('button[aria-label="Hotels"]'); } async function back(page) { await page.click('[aria-label="Clear search"]'); } module.exports = {action, back, url};
現(xiàn)在使用下面的命令運(yùn)行上面的js代碼, 當(dāng)memlab與web頁(yè)面進(jìn)行交互時(shí)就會(huì)運(yùn)行內(nèi)置的泄漏檢測(cè)器檢測(cè)內(nèi)存泄漏。
memlab run --scenario test-google-maps.js
執(zhí)行結(jié)束之后,Memlab就會(huì)打印內(nèi)存泄漏結(jié)果,顯示每個(gè)泄漏對(duì)象集群的一個(gè)代表性保留跟蹤。
MemLab found 46 leak(s) --Similar leaks in this run: 4-- --Retained size of leaked objects: 8.3MB-- [Window] (native) @35847 [8.3MB] --20 (element)---> [InternalNode] (native) @130981728 [8.3MB] --8 (element)---> [InternalNode] (native) @130980288 [8.3MB] --1 (element)---> [EventListener] (native) @131009888 [8.3MB] --1 (element)---> [V8EventListener] (native) @224808192 [8.3MB] --1 (element)---> [eventHandler] (closure) @168079 [8.3MB] --context (internal)---> [<function scope>] (object) @181905 [8.3MB] --bigArray (variable)---> [Array] (object) @182925 [8.3MB] --elements (internal)---> [(object elements)] (array) @182929 [8.3MB] ...
接著,我們就可以通過(guò)這些捕獲的跟蹤信息定位到里面的方法。
當(dāng)然,我沒(méi)也可以使用Memlab查看基于從Chromium、Hermes、memlab或任何node.js或electronic .js程序中獲取的單個(gè)JavaScript堆快照檢測(cè)到的內(nèi)存問(wèn)題。
memlab view-heap --snapshot <PATH TO .heapsnapshot FILE>
然后,我沒(méi)可以使用對(duì)象的id,比如node-id @28173來(lái)精確定位特定的堆對(duì)象。
當(dāng)然,Memlab也支持自定義的檢漏器,自定義檢漏器時(shí)需要在場(chǎng)景文件中添加一個(gè)filterLeak文檔。對(duì)于目標(biāo)交互分配的每個(gè)未釋放的堆對(duì)象(節(jié)點(diǎn))將調(diào)用filterLeak。
function filterLeak(node, heap) { // ... your leak detector logic // return true to mark the node as a memory leak };
heap是最終JavaScript堆快照的圖形表示。
3.2 堆分析與研究
除了檢測(cè)內(nèi)存泄露意外,Memlab還提供了很多其他有用的命令,比如查看某個(gè)對(duì)象在運(yùn)行的交互過(guò)程中的整個(gè)鏈路。
memlab analyze unbound-object
獲取V8/hermes .heapsnapshot文件。
memlab analyze unbound-object --snapshot-dir <DIR_OF_SNAPSHOT_FILES>
使用memlab analyze查看所有內(nèi)置內(nèi)存分析。
memlab trace --node-id <HEAP_OBJECT_ID>
3.3 Memlab API
Memlab的npm包支持在瀏覽器中啟動(dòng)端到端運(yùn)行并檢測(cè)內(nèi)存泄漏。
const memlab = require('memlab'); const scenario = { url: () => 'https://www.google.com/maps/@37.386427,-122.0428214,11z', action: async (page) => await page.click('button[aria-label="Hotels"]'), back: async (page) => await page.click('[aria-label="Clear search"]'), } memlab.run({scenario});
3.4 內(nèi)存斷言
Memlab支持在Node.js程序中進(jìn)行Jest測(cè)試,也可以使用圖視圖API來(lái)獲得其自身狀態(tài)的堆圖視圖,執(zhí)行自?xún)?nèi)存檢查,并編寫(xiě)各種內(nèi)存斷言。
import type {IHeapSnapshot} from '@memlab/core'; import {config, takeNodeMinimalHeap, tagObject} from '@memlab/core'; test('memory test', async () => { config.muteConsole = true; const o1 = {}; let o2 = {}; tagObject(o1, 'memlab-mark-1'); tagObject(o2, 'memlab-mark-2'); o2 = null; const heap: IHeapSnapshot = await takeNodeMinimalHeap(); //斷言函數(shù) expect(heap.hasObjectWithTag('memlab-mark-1')).toBe(true); //斷言函數(shù) expect(heap.hasObjectWithTag('memlab-mark-2')).toBe(false); }, 30000);
以上就是Meta開(kāi)源JavaScript內(nèi)存泄漏監(jiān)測(cè)工具M(jìn)emLab安裝使用的詳細(xì)內(nèi)容,更多關(guān)于JavaScript內(nèi)存泄漏監(jiān)測(cè)MemLab的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
smartbanner.js實(shí)現(xiàn)可定制智能應(yīng)用橫幅使用示例
這篇文章主要為大家介紹了smartbanner.js實(shí)現(xiàn)可定制智能應(yīng)用橫幅使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03微信小程序 中wx.chooseAddress(OBJECT)實(shí)例詳解
這篇文章主要介紹了微信小程序 中wx.chooseAddress(OBJECT)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-03-03JavaScript loader原理簡(jiǎn)單總結(jié)示例解析
這篇文章主要為大家介紹了JavaScript loader原理簡(jiǎn)單總結(jié)示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08微信小程序動(dòng)態(tài)的加載數(shù)據(jù)實(shí)例代碼
這篇文章主要介紹了 微信小程序動(dòng)態(tài)的加載數(shù)據(jù)實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-04-04微信小程序 開(kāi)發(fā)MAP(地圖)實(shí)例詳解
這篇文章主要介紹了微信小程序 開(kāi)發(fā)MAP(地圖)實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06Javascript閉包使用場(chǎng)景原理詳細(xì)
這篇文章主要介紹了Javascript閉包的使用場(chǎng)景, 由于在Javascript語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁,下面一起進(jìn)入文化在哪個(gè)了解文章內(nèi)容2021-11-11JavaScript?CSS優(yōu)雅實(shí)現(xiàn)網(wǎng)頁(yè)多主題風(fēng)格換膚功能詳解
這篇文章主要為大家介紹了JavaScript?CSS優(yōu)雅的實(shí)現(xiàn)網(wǎng)頁(yè)多主題風(fēng)格換膚功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02