修正記憶體問題

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

摘要

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

總覽

根據 RAIL 效能模型的精神,您應以使用者為效能努力的目標。

記憶體問題非常重要,因為使用者經常會察覺到這類問題。使用者可能會以下列方式感知記憶體問題:

  • 網頁的成效會隨著時間流逝而逐漸惡化。這可能是記憶體耗損的症狀。記憶體流失是指網頁中的錯誤導致網頁在一段時間內逐漸使用越來越多的記憶體。
  • 網頁成效持續不佳。這可能是記憶體膨脹的症狀。記憶體膨脹是指網頁使用的記憶體超過達到最佳網頁速度所需的記憶體。
  • 網頁載入速度變慢或似乎經常暫停。這可能是經常進行垃圾收集的症狀。垃圾收集是指瀏覽器回收記憶體的過程。瀏覽器會決定何時發生這種情況。在收集期間,系統會暫停所有指令碼執行作業。因此,如果瀏覽器經常執行垃圾收集,指令碼執行作業就會經常暫停。

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

記憶體流失很容易定義。如果網站逐漸使用越來越多的記憶體,就表示發生記憶體流失。不過,記憶體膨脹問題較難找出原因。什麼情況下會「使用過多記憶體」?

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

重點是使用 RAIL 模型,並將重點放在使用者身上。找出使用者常用的裝置,然後在這些裝置上測試網頁。如果使用體驗一貫不佳,表示網頁可能超出這些裝置的記憶體能力。

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

請使用 Chrome 工作管理員做為記憶體問題調查的起點。工作管理員是即時監控工具,可告知網頁使用的記憶體量。

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

    開啟工作管理員。

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

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

這兩個資料欄會提供不同資訊,說明網頁如何使用記憶體:

  • 「Memory footprint」欄代表作業系統記憶體。DOM 節點會儲存在作業系統記憶體中。如果這個值增加,表示系統正在建立 DOM 節點。
  • 「JavaScript 記憶體」欄代表 JS 堆積。這個欄包含兩個值。您感興趣的值是即時數字 (括號內的數字)。即時數字代表網頁上可存取物件使用的記憶體用量。如果這個數字增加,表示系統正在建立新物件,或是現有物件數量增加。

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

使用效能記錄功能,以圖表呈現記憶體外洩

您也可以使用「成效」面板,作為調查的另一個起點。「效能」面板可讓您以視覺化方式呈現網頁在一段時間內的記憶體用量。

  1. 在開發人員工具中開啟「效能」面板。
  2. 勾選「Memory」核取方塊。
  3. 錄製音訊

如要示範效能記憶體記錄,請考慮使用下列程式碼:

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 陣列。執行這個程式碼會產生類似下圖的時間軸記錄:

簡單的成長範例。

首先,說明使用者介面。「總覽」窗格中的「HEAP」圖表 (位於「NET」下方) 代表 JS 堆積。「總覽」窗格下方是「計數器」窗格。您可以在這裡查看按 JS 堆積區 (與「Overview」窗格中的「HEAP」圖表相同)、文件、DOM 節點、事件監聽器和 GPU 記憶體細分的記憶體用量。停用核取方塊會將其從圖表中隱藏。

接下來,我們將分析程式碼並與螢幕截圖進行比較。查看節點計數器 (綠色圖表) 時,您會發現它與程式碼完全一致。節點數量會以不連續的步驟增加。您可以假設每增加一個節點計數,就代表呼叫 grow()。JS 堆積圖 (藍色圖表) 則不那麼直觀。根據最佳做法,第一個下降點實際上是強制垃圾收集 (按下「collect garbage」按鈕即可執行)。隨著錄製進度,您可以看到 JS 堆積大小出現尖峰。這是正常且預期的結果:JavaScript 程式碼會在每次按下按鈕時建立 DOM 節點,並在建立一百萬個字元的字串時執行大量工作。重點在於 JS 堆疊結束時的值高於開始時的值 (此處的「開始」是指強制垃圾收集後的時間點)。在實際情況中,如果您發現這種增加 JS 堆積大小或節點大小的模式,可能表示發生記憶體流失。

使用堆積圖快照找出未連結的 DOM 樹狀結構記憶體流失情形

只有在網頁的 DOM 樹狀結構或 JavaScript 程式碼中沒有 DOM 節點的參照時,系統才會回收該節點。當節點從 DOM 樹狀結構中移除,但仍有部分 JavaScript 參照該節點時,就稱為「分離」節點。卸離的 DOM 節點是記憶體耗損的常見原因。本節將說明如何使用 DevTools 堆積分析工具來找出已分離的節點。

以下是分離 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 節點之間如何分配記憶體。

如要建立快照,請開啟「DevTools」,前往「Memory」面板,選取「Heap Snapshot」圓形按鈕,然後按下「Take snapshot」按鈕。

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

系統可能需要一些時間處理及載入快照。完成後,請從左側面板中選取該項目 (名稱為「Heap 快照」)。

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

篩選已分離的節點。

展開圓點即可查看已分離的樹狀結構。

檢查已分離的樹狀圖。

按一下節點即可進一步調查。在「Objects」窗格中,您可以進一步瞭解參照該物件的程式碼。例如,在下列螢幕截圖中,您可以看到 detachedTree 變數參照節點。如要修正這類記憶體外洩問題,您必須研究使用 detachedTree 的程式碼,並確保該程式碼在不再需要節點時移除對該節點的參照。

調查已分離的節點。

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

分配時間軸是另一個可用來追蹤 JS 堆積記憶體流失情況的工具。

如要示範分配時間表,請考慮使用下列程式碼:

var x = [];

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

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

每次按下程式碼中參照的按鈕時,系統就會將長度為一百萬個字元的字串新增至 x 陣列。

如要記錄分配時間軸,請開啟「DevTools」,前往「Memory」面板,選取「Allocations on timeline」圓形按鈕,按下「Record」 按鈕,執行您懷疑會造成記憶體耗盡的動作,然後按下「Stop recording」按鈕。

錄製時,請留意「分配時間軸」是否顯示任何藍色長條,如以下螢幕截圖所示。

成效時間軸中的新分配。

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

縮放的分配時間軸。

展開物件並按一下其值,即可在「物件」窗格中查看更多詳細資料。舉例來說,在下方螢幕截圖中,您可以查看新分配的物件詳細資料,瞭解該物件已分配至 Window 範圍中的 x 變數。

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

依函式調查記憶體配置

使用「記憶體」面板中的「配置取樣」設定檔類型,查看 JavaScript 函式記憶體配置情形。

「Memory」面板中的配置取樣分析器。

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

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

分配設定檔結果頁面。

找出由 JS 參照保留的物件

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

記錄分離元素設定檔,即可查看確切的 HTML 節點和節點數量。

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

找出頻繁的垃圾收集作業

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

您可以使用 Chrome 工作管理員或時間軸記憶體記錄,找出經常發生的垃圾收集情形。在工作管理員中,如果「記憶體」或「JavaScript 記憶體」值經常上升和下降,表示垃圾收集頻繁。在時序記錄中,經常上升和下降的 JS 堆積或節點計數圖表,表示垃圾收集頻繁執行。

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