有時候網頁會「越用越慢」。除了 JavaScript 與繪製,過量記憶體使用也是常見的原因。本章介紹監控記憶體用量、找出記憶體洩漏(Memory Leak)的工具與流程。

建構視角的對應章節:記憶體管理

垃圾回收(Garbage Collection)#

瀏覽器運作中會因為新增 DOM、JavaScript 物件等需要分配記憶體。為了避免無限累積,瀏覽器會自行決定時機進行垃圾回收(Garbage Collection, GC)。基本原則是:執行 GC 時,若沒有任何路徑可以存取到某物件,該物件所佔的記憶體就會被釋放。

let a = {}; // 分配 Memory 來儲存 A 物件
let b = {}; // 分配 Memory 來儲存 B 物件
let c = a;

a = undefined;
b = undefined;

此時若執行 GC:B 物件已無任何路徑可存取,會被回收;A 物件仍可透過 c 存取,所以不會被釋放。

值得注意的是 GC 在主執行緒(Main Thread)上進行,會佔用 JavaScript 與 rendering 的執行時間。

分析工具#

DevTools 中與記憶體相關的工具主要有:

  • Performance:開啟 Memory 選項後紀錄記憶體基本走勢。
  • Performance Monitor:即時監看當前頁面狀態。
  • Memory:提供更詳盡的記憶體紀錄與分析能力。

Performance 的 Memory 選項#

開啟後紀錄會多出一條記憶體用量曲線,並在最上方追加 HEAP 圖表呈現整體 JavaScript 記憶體變化。點擊圖中某時刻會自動標出主執行緒上最近的 task 或 JavaScript 區塊。

Collect garbage#

工具列上的垃圾桶按鈕可以強制執行 GC。一個常見技巧是反覆執行某個 function 並在每次後立即強制 GC,如果 HEAP 不會回到原本水準,就是記憶體洩漏的警訊。

高頻率 GC#

不一定意味著洩漏,但代表分配量過多。GC 會佔用主執行緒,過於頻繁就會反過來拖慢效能,這時應從程式邏輯上想辦法降低分配量。

Performance Monitor#

按下 ESC 開啟 Drawer 即可找到 Performance Monitor,它會即時顯示效能與資源資訊。例如可以觀察強制 GC 後 DOM Nodes 數量是否大幅下降。

Memory 面板#

Memory 面板提供三種測量方式:

  • Heap Snapshot:紀錄當下完整的記憶體狀態,可錄多次比較。
  • Allocation Timeline:詳細紀錄一段時間內的分配與用量。
  • Allocation Sampling:以 function 為單位紀錄分配狀況。

開始介紹工具前,先理解幾個重要欄位:

  • Distance:從 root(window)出發,存取到該物件的最短路徑。
  • Shallow size:物件本身佔用的記憶體;陣列或字串通常較大。
  • Retained size:該物件被 GC 時可釋放的總量,包含所有以它為 dominator 的物件。
  • Dominator:若 A 是 B 的 dominator,則 A 被 GC 時,B 也無路徑可達、會一起被 GC;B 可以有多個 dominator。

Heap Snapshot#

選擇 Heap Snapshot 後按下 Take Snapshot,瀏覽器會先 GC 再紀錄當下的 JavaScript 與 DOM 記憶體狀態,也可比較多份 snapshot 找差異。

Summary view#

預設模式以 constructor 分類,能快速看出哪些 constructor 佔用較多記憶體。每個物件右側 @ 開頭的數字是唯一 ID,點擊後下方會展開 retainers(可達該物件的物件),第一層通常就是變數名稱,常常是線索的關鍵。

上方的 class filter 可用來過濾 constructor。某些名稱有特殊意義,例如 Detached HTMLElement 代表已不在 DOM 中、卻仍可被 JavaScript 存取的元素,常常是記憶體洩漏的來源。

Comparison view#

在某行為前後分別取 snapshot,比對之間已釋放、新分配的記憶體與物件數量,鎖定可能造成洩漏的位置。

Containment view#

window 為起點層層向下展開,適合一層層尋找可疑來源。

Allocation Timeline#

比 Heap Snapshot 多出一條時間軸,以長條圖顯示記憶體隨時間的分配狀況。藍色條代表沒有被 GC 的分配,鎖定最高的幾條再縮小時間範圍,就能聚焦問題來源。但這種紀錄的 overhead 較高,記憶體用量大時報告生成也會很慢。

Allocation Sampling#

以 function 為單位紀錄記憶體狀況,overhead 比 Allocation Timeline 小得多,適合長時間紀錄與找出記憶體用量大的 function。提供 Chart、Bottom Up、Top Down 三種觀察模式。

結語#

不需要在每行程式碼都精打細算,反而要關注是否有記憶體洩漏的可能、或在出現效能瓶頸時針對性地分析。介紹了那麼多工具,重點仍是「鎖定問題、針對優化」。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10250174