iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
1

有時候網頁會越跑越慢,到底發生了甚麼事情呢?

概覽

拖慢網頁效能的原因除了 JavaScript、Rendering 外,還有個可能就是用了過多的記憶體,尤其網頁的效能隨著使用時間越來越差時就是個明顯的徵兆,以下將會介紹網頁中監控 Memory 用量的方式以及如何找出 Memory 引發的效能問題,搭配文末的實際範例來加強理解。

Garbage Collection

瀏覽器運作的過程中會因為新建 DOM、JavaScipt 建立物件等等原因使用到 Memory,為了避免佔用過多 Memory 會自動執行 GC,執行的時機則由瀏覽器自行判斷。

GC 的機制可以簡單理解為每次新建物件、DOM 時,瀏覽器都會分配額外的 Memory 去儲存,執行 GC 時只要沒有任何方式可以存取到某個物件,該物件所用的 Memory 就會被釋放。

簡單範例:

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

a = undefined;
b = undefined;

// 此時執行 GC 能夠回收多少記憶體?

由於已經沒有任何方式可以存取到 B 物件,該物件佔用的 Memory 會被釋放,而 a 雖然被修改為 undefined,A 物件還是能夠透過 c 存取,因此不會被釋放。

此外 GC 是在主線程中進行,也就是說會佔用到 JavaScript、Rendering 的執行時間。

分析、監測工具

DevTools 中和 Memory 相關的工具主要有:

  • Performance – Performance 面板可以開啟 Memory 選項,記錄效能時會涵蓋簡單的 Memory 資訊。
  • Performance monitor – 及時監測目前網頁的效能資訊
  • Memory – 更詳細的 Memory 資訊紀錄、分析功能

Performance

在 Performance 開啟 Memory 選項後產生的效能報告會多出一列記憶體用量紀錄,同時最上方也會多出一小列 HEAP 圖表顯示整體 JavaScript Memory 的用量變化。

此外點擊下方大圖表中的某個時間點後會自動標記主線程中離該時間最近的 Task 或 JavaScript 區塊,關於 JavaScript 執行的效能分析,可以參考 Performance - Analyze Runtime Activities。

圖表上方的 Checkboxes 可以開關各個種類的 memory 資訊

Collect garbage (GC)

此外面板的工具列有一個垃圾桶(Collect garbage),紀錄效能的過程中按下按鈕會強制 GC,可以藉此看出執行一個 Function 前後的記憶體差別,例如每次執行某個 Function 後馬上強制 GC,如果 HEAP 量沒有回到執行前的量,就是 Memory leak 的警訊。

高頻率 GC

不一定造成 Memory leak,但它是 Memory 用量過多的警訊,由於 GC 會在主線程中進行,過於頻繁的 GC 會嚴重影響效能,可能需要改變程式碼的寫法。

Performance monitor

按下 ESC 打開 Drawer,選項中可以找到 Performance monitor,用來即時監測效能資訊。

強制 GC 時 DOM Nodes 少了一半

Memory

Memory 面板中有三種測量方式:

  • Heap snapshot – 記錄當下的詳細 Memory 資訊,可以記錄多次來進行比較
  • Allocation instumentation on timeline - 詳細記錄一段時間的記憶體用量和分配狀況,
  • Allocation sampling - 記錄 JavaScript 執行時的記憶體用量

在開始介紹工具前,首先得先說明一些 Memory 分析的重要知識和功能解釋:

  • Distance - 從 Root 起算(window),存取到該物件的最短路徑
  • Shallow size - 物件本身所用的 Memory,通常 Array 或字串的記憶體用量較大
  • Retained size - 物件被 GC 時能夠釋放的記憶體總量,GC 時會包含 Dominator 為該物件的所有物件。
  • Dominator - 若 A 物件為 B 物件的 Dominator,則 A 被 GC 時就沒有可以存取到 B 的方式,B 也會被 GC,且 B 可能有多個 Dominator。

以下圖為例,箭頭是物件的存取路徑,Root 可以存取 #3#1 兩個物件,因此它們的 Distance 皆是 1,以 #10 來說,#10 的 Dominator 有 #3#7,當 #3 被 GC 時底下的 #7 ~ #10 都會被 GC。

 

Heap Snapshot

選擇 Heap snapshot 後按下 Take snapshot 會執行一次 GC 並記錄當下的 JavaScript 和 DOM Memory 用量,還可以比較不同 Snapshot 的之間的差異,用來分析 Memory 已經很夠用。

Summary view

預設的分析模式是 Summary,用 Constructor 來分類所有物件,很容易就可以看出哪些 Constructor 的 Memory 用量比較大,也適合用來分析特定物件類型的 Memory 用量。

此外物件的右側有 @ 開頭的數字,代表物件的唯一 ID,點擊物件後下方會展開物件的 Retainers(可以連結到該物件的物件),第一層可以看到該物件的名稱,常常會是問題的關鍵。

array 就是變數名稱

上方的 Class filter 可以用來過濾 Constructor,Summary 中某些 Constructor 會有特殊名稱,例如 Detached HTMLElement 就是已經不在 DOM 中但仍可被 JavaScript 存取的物件,Detached DOM 也常常是 Memory 的來源之一。

除了過濾 Constructor 之外,按下 Cmd+F 也可以進行一般的搜尋。

Comparison view

在一個行為前後分別進行 Snapshot,透過比較兩個 Snapshots 中已釋放、已佔用的 Memory 和建立的物件數量差別來分析 Memory leak 的原因。

Containment view

window 為底向下展開連結的物件(Distance 為 1 的物件),適合用來一層一層尋找 Memory leak 的因素。

 

Allocation instrumentation on timeline

比起 Heap snapshot,上方會多出一列時間軸,以長條圖顯示 Memory 隨著時間分配的狀況,藍線代表沒有被 GC 的 Memory 配置,鎖定最高的幾條藍線,把時間區段縮小就能更容易找出問題的原因。

用 Demo 頁面跑這個要跑超~久

壞處是這種記錄方式的 Overhead 較高,影響瀏覽器的效能較為明顯,且 Memory 用量較大時報告製作的速度會非常慢。

 

Allocation sampling

以 Function 為單位記錄 Memory 的狀態,比起 Timeline 記錄 Memory 資訊時的 Overhead 小很多,適合較長時間的記錄以及找出 Memory 用量較多的 Function。

Allocation sampling 中有 Chart、Bottom Up、Top Down 三種觀察模式,此三種模式的分析方法可以參考 Performance - Analyze Runtime Activities。

 

實戰演練

建議開啟 Demo 頁面 Performance - Memory Leak,利用 Performance 面板的強制 GC 搭配 Performance monitor 來觀察 JS heap 和 Nodes 的變化,並試著用剛剛提到的方式來分析 Memory 問題。

 

結語

雖然介紹了那麼多的 Memory 監控方式,但其實不用在寫每一行程式碼時都去計較記憶體的用量,反倒需要注意有沒有 Memory leak 的可能,或是出現效能瓶頸時再找出問題,針對性的優化,會是比較有效率的做法。


上一篇
[Day 23] Performance - Analyze Paint & Layers
下一篇
[Day 25] Performance - Analyze Runtime Activities
系列文
你所不知道的各種前端 Debug 技巧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言