修正記憶體問題

Kayce Basques
Kayce Basques

瞭解如何使用 Chrome 和開發人員工具找出影響網頁效能的記憶體問題,包括記憶體流失、記憶體膨脹和頻繁的垃圾收集。

摘要

  • 使用 Chrome 工作管理員,查看網頁的記憶體用量。
  • 使用時間軸記錄,以視覺化方式呈現記憶體用量變化。
  • 使用堆積快照找出卸離的 DOM 樹狀結構 (記憶體流失的常見原因)。
  • 使用「配置時間軸」記錄,找出 JS 堆積中何時會配置新的記憶體。
  • 找出由 JavaScript 參照保留的已卸離元素。

總覽

根據 RAIL 效能模型的精神,效能改善工作的重點應放在使用者身上。

記憶體問題很重要,因為使用者通常會察覺到這類問題。使用者可以透過下列方式察覺記憶體問題:

  • 網頁成效隨著時間推移而逐漸惡化。這可能是記憶體流失的徵兆。記憶體流失是指網頁中的錯誤導致網頁隨著時間逐漸使用越來越多記憶體。
  • 網頁效能持續不佳。這可能是記憶體膨脹的症狀。記憶體膨脹是指網頁使用的記憶體超過最佳 Page Speed 所需的記憶體。
  • 頁面效能延遲或似乎經常暫停。這可能是頻繁執行垃圾收集的徵兆。垃圾收集是指瀏覽器回收記憶體的過程。瀏覽器會決定何時執行這項操作。收集期間,所有指令碼執行作業都會暫停。因此,如果瀏覽器經常收集垃圾,指令碼執行就會經常暫停。

記憶體膨脹:多少才算「過多」?

記憶體流失很容易定義。如果網站使用的記憶體越來越多,就表示發生記憶體流失。但記憶體膨脹問題較難解決。什麼情況屬於「耗用過多記憶體」?

由於不同裝置和瀏覽器的功能不盡相同,因此沒有確切的數字。同一個網頁在高階智慧型手機上可能運作順暢,但在低階智慧型手機上可能會當機。

重點在於使用 RAIL 模型,並以使用者為中心。找出使用者常用的裝置,然後在這些裝置上測試網頁。如果體驗持續不佳,表示網頁可能超出這些裝置的記憶體容量。

使用 Chrome 工作管理員即時監控記憶體用量

請先使用 Chrome 工作管理員調查記憶體問題。工作管理員是即時監控工具,可顯示網頁使用的記憶體量。

  1. 按下 Shift+Esc 鍵,或前往 Chrome 主選單,然後依序選取「更多工具」>「工作管理員」,開啟工作管理員。

    開啟工作管理員。

  2. 在工作管理員的表格標題上按一下滑鼠右鍵,然後啟用「JavaScript 記憶體」。

    在工作管理員標頭中啟用 JS 記憶體。

這兩欄會顯示網頁使用記憶體的不同資訊:

  • 「記憶體用量」欄代表 OS 記憶體。DOM 節點會儲存在 OS 記憶體中。如果這個值持續增加,表示系統正在建立 DOM 節點。
  • 「JavaScript Memory」欄代表 JS 堆積。這個資料欄包含兩個值。您需要關注的值是即時數字 (括號中的數字)。「live」數字代表網頁上可連線物件使用的記憶體量。如果這個數字增加,表示正在建立新物件,或現有物件正在增加。

    工作管理員,已啟用 JavaScript 記憶體標頭。

使用效能錄製功能,以圖表呈現記憶體流失

您也可以從「Performance」面板開始調查。「Performance」(效能) 面板可協助您以圖表呈現網頁在一段時間內的記憶體用量。

  1. 在開發人員工具中開啟「Performance」面板。
  2. 勾選「記憶體」核取方塊。
  3. 錄製內容。

為了示範「Performance」記憶體記錄,請參考下列程式碼:

var x = [];

function grow() {
  for (var i = 0; i < 10000; i++) {
    document.body.appendChild(document.createElement('div'));
  }
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

每當按下程式碼中參照的按鈕,就會有一萬個 div 節點附加至文件主體,並將一百萬個 x 字元的字串推送至 x 陣列。執行這段程式碼會產生時間軸記錄,如下方螢幕截圖所示:

簡單的成長範例。

首先,我們將說明使用者介面。「Overview」窗格中的「HEAP」圖表 (位於「NET」下方) 代表 JS 堆積。「Overview」窗格下方是「Counter」窗格。您可以在這裡查看記憶體用量,細分為 JS 堆積 (與「Overview」窗格中的「HEAP」圖表相同)、文件、DOM 節點、事件監聽器和 GPU 記憶體。取消勾選核取方塊,即可在圖表中隱藏該項目。

現在,我們來比較程式碼和螢幕截圖。查看節點計數器 (綠色圖表) 時,您會發現它與程式碼完全一致。節點數量會以離散步驟增加。您可以假設節點數量每次增加,都是對 grow() 的呼叫。JS 堆積圖 (藍色圖表) 則不那麼直觀。為遵循最佳做法,第一個下降點實際上是強制垃圾收集 (按下「collect garbage」按鈕即可達成)。隨著錄製作業進行,您會看到 JS 堆積大小急遽上升。這是正常且預期的情況:JavaScript 程式碼會在每次點擊按鈕時建立 DOM 節點,並在建立一百萬個字元的字串時執行大量工作。這裡的重點是,JS 堆積結束時的值高於開始時的值 (這裡的「開始」是指強制垃圾回收後的點)。在實際情況下,如果看到 JS 堆積大小或節點大小增加,可能表示記憶體流失。

使用堆積快照找出分離的 DOM 樹狀結構記憶體流失

只有在網頁的 DOM 樹狀結構或 JavaScript 程式碼中,都沒有 DOM 節點的參照時,才能對該節點進行垃圾收集。如果節點已從 DOM 樹狀結構中移除,但仍有部分 JavaScript 參照該節點,則稱為「分離」節點。卸離的 DOM 節點是記憶體流失的常見原因。本節將說明如何使用開發人員工具的堆積分析器,找出分離節點。

以下是分離 DOM 節點的簡單範例。

var detachedTree;

function create() {
  var ul = document.createElement('ul');
  for (var i = 0; i < 10; i++) {
    var li = document.createElement('li');
    ul.appendChild(li);
  }
  detachedTree = ul;
}

document.getElementById('create').addEventListener('click', create);

點選程式碼中參照的按鈕,即可建立含有十個 li 子項的 ul 節點。程式碼會參照這些節點,但 DOM 樹狀結構中沒有這些節點,因此會遭到卸離。

堆積快照是找出分離節點的方法之一。顧名思義,堆積快照會顯示在快照當下,網頁的 JS 物件和 DOM 節點之間的記憶體分布情形。

如要建立快照,請開啟開發人員工具,前往「Memory」面板,選取「堆積快照」圓形按鈕,然後按下「Take snapshot」按鈕。

已選取「Take heap snapshot」圓形按鈕。

快照可能需要一些時間處理及載入。完成後,請從左側面板 (名為「堆積快照」) 選取該快照。

在「Class filter」輸入框中輸入 Detached,搜尋已分離的 DOM 樹狀結構。

篩選分離節點。

展開插入符號,調查分離的樹狀結構。

調查分離樹狀結構。

按一下節點即可進一步調查。在「Objects」窗格中,您可以查看更多關於參照該物件的程式碼資訊。舉例來說,在下方螢幕截圖中,您可以看到 detachedTree 變數參照節點。如要修正這個特定的記憶體流失問題,請研究使用 detachedTree 的程式碼,並確保在不再需要時移除對節點的參照。

調查分離節點。

使用配置時間軸找出 JS 堆積記憶體流失

Allocation Timeline 是另一項工具,可協助您追蹤 JS 堆積中的記憶體流失。

如要示範「Allocation Timeline」,請參考下列程式碼:

var x = [];

function grow() {
  x.push(new Array(1000000).join('x'));
}

document.getElementById('grow').addEventListener('click', grow);

每當按下程式碼中參照的按鈕時,系統就會在 x 陣列中加入一串一百萬個字元的字串。

如要記錄分配時間軸,請開啟開發人員工具,前往「記憶體」面板,選取「時間軸上的分配」圓形按鈕,按下 「記錄」按鈕,執行您認為會導致記憶體流失的操作,完成後再按下 「停止錄製」按鈕。

錄製時,請留意「Allocation Timeline」中是否顯示任何藍色長條,如下方螢幕截圖所示。

成效時間軸中的新分配。

藍色長條代表新的記憶體配置。這些新的記憶體配置就是記憶體流失的候選項目。您可以放大長條,篩選「Constructor」窗格,只顯示在指定時間範圍內分配的物件。

放大後的分配時間軸。

展開物件並點選其值,即可在「Object」窗格中查看更多詳細資料。舉例來說,在下方螢幕截圖中,查看新分配物件的詳細資料,即可看到該物件已分配給 Window 範圍中的 x 變數。

字串陣列的物件詳細資料。

依函式調查記憶體配置

在「Memory」面板中,使用「Allocation sampling」設定檔類型,查看 JavaScript 函式的記憶體配置。

「記憶體」面板中的配置取樣分析器。

  1. 選取「Allocation sampling」圓形按鈕。如果頁面上有 worker,您可以在「Select JavaScript VM instance」視窗中選取該 worker 做為分析目標。
  2. 按下「開始」按鈕。
  3. 在要調查的頁面上執行操作。
  4. 完成所有動作後,請按下「停止」按鈕。

開發人員工具會依函式顯示記憶體配置細目。預設檢視畫面為「Heavy (Bottom Up)」,會將分配最多記憶體的函式顯示在頂端。

分配設定檔結果頁面。

找出由 JS 參照保留的物件

「Detached elements」設定檔會顯示因 JavaScript 程式碼參照而保留的已卸離元素。

記錄「Detached elements」設定檔,查看確切的 HTML 節點和節點數量。

已卸離的元素設定檔範例。

找出頻繁的垃圾資訊收集作業

如果網頁經常暫停,可能是垃圾收集問題。

您可以使用 Chrome 工作管理員或時間軸記憶體記錄,找出頻繁的垃圾回收。在工作管理員中,如果「記憶體」或「JavaScript 記憶體」值頻繁升降,代表垃圾收集作業頻繁。在時間軸記錄中,如果 JS 堆積或節點數量圖表頻繁上升和下降,表示垃圾收集頻率很高。

找出問題後,您可以使用配置時間軸記錄,找出記憶體配置的位置,以及導致配置的函式。