記錄堆積快照

Meggin Kearney
Meggin Kearney
Sofia Emelianova
Sofia Emelianova

請依序前往「記憶體」 >「設定檔」 >「堆積快照」,瞭解如何記錄堆積快照,以及找出記憶體流失情形。

堆積分析器會根據網頁的 JavaScript 物件和相關 DOM 節點顯示記憶體分佈情形。可用於拍攝 JS 堆積快照、分析記憶體圖表、比較快照及找出記憶體流失情形。詳情請參閱物件保留樹狀結構

拍攝快照

如要拍攝堆積快照,請按照下列步驟操作:

  1. 在要分析的頁面上開啟開發人員工具,然後前往「Memory」面板。
  2. 選取 radio_button_checked「堆積快照」剖析類型,然後選取 JavaScript VM 執行個體,然後點選「拍攝快照」

所選剖析類型和 JavaScript VM 執行個體。

「記憶體」面板載入並剖析快照時,會在「HEAP SNAPSHOTS」部分的快照標題下方,顯示可存取的 JavaScript 物件總大小。

可連線物件的總大小。

快照只會顯示可從全域物件存取的記憶體圖表中的物件。拍攝快照的第一步一律是垃圾收集。

零散項目物件的堆積快照。

清除快照

如要移除所有快照,請按一下「block」(封鎖所有快照)

清除所有設定檔。

查看快照

如要查看不同用途的快照,請在頂端的下拉式選單中選取任一檢視表:

查看 內容 目的
摘要 按建構函式名稱分組的物件。 您可以根據類型尋找物件及其記憶體用量。有助於追蹤 DOM 流失情形
比較 兩個快照之間的差異。 可用來比較作業前後的兩個 (或多個) 快照。檢查釋出的記憶體和參照計數中的差異,確認是否有記憶體流失的存在和原因。
遏制 堆積內容 提供更完善的物件結構檢視畫面,並協助分析全域命名空間 (視窗) 中參照的物件,從中找出保留物件的內容。您可以用這個功能分析閉合情況,並在低階的情況下深入瞭解物件。
統計資料 記憶體配置的圓餅圖 查看分配給程式碼、字串、JS 陣列、型別陣列和系統物件的記憶體部分實際大小。

從頂端的下拉式選單中,選取「摘要」資料檢視。

摘要檢視

最初,堆積快照會在「Summary」檢視畫面中開啟,該檢視畫面會列出資料欄中的「建構函式」。您可以展開建構函式,查看其例項化的物件。

含有已展開建構函式的「Summary」檢視畫面。

如要篩除不相關的建構函式,請在「Summary」檢視畫面頂端的「Class 篩選器」中輸入要查看的名稱。

建構函式名稱旁邊的數字表示使用建構函式建立的物件總數。摘要檢視畫面也會顯示下列資料欄:

  • 距離:以最短的節點路徑顯示與根層級的距離。
  • 淺層大小會顯示由特定建構函式建立的所有物件的淺層大小總和。淺層大小是指物件本身保留的記憶體大小。一般來說,陣列和字串的淺層大小通常較大。另請參閱物件大小
  • 保留大小會顯示同一組物件之間的最大保留大小。保留大小是指刪除物件後無法再存取依附性而釋出的記憶體大小。另請參閱物件大小

展開建構函式時,「Summary」(摘要) 檢視畫面會顯示該建構函式的所有執行個體。每個執行個體都會在對應的資料欄中收到其淺層和保留大小的細目。@ 字元之後的數字是物件的專屬 ID。可讓您比較每個物件的堆積快照。

建構函式篩選器

Summary 檢視畫面可讓您根據記憶體效率不佳的常見情況篩選建構函式。

如要使用這些篩選器,請在動作列最右側的下拉式選單中選取下列任一選項:

  • 所有物件:目前快照擷取的所有物件。預設為設定。
  • 快照 1 前配置的物件:在第一個快照拍攝之前,建立並保留在記憶體中的物件。
  • 在快照 1 和快照 2 之間配置的物件:查看最新快照與上一個快照之間的物件差異。每新增一個快照,這個篩選器的累進時間就會增加到下拉式清單中。
  • 重複的字串:在記憶體中多次儲存的字串值。
  • 卸離節點所保留的物件:因卸離的 DOM 節點參照,因此保留的物件。
  • 開發人員工具控制台保留的物件:透過開發人員工具控制台評估或互動的物件,因此會保留在記憶體中。

摘要中的特殊項目

除了依建構函式分組之外,Summary 檢視畫面也會依以下條件將物件分組:

  • 內建函式,例如 ArrayObject
  • 您在程式碼中定義的函式。
  • 非以建構函式為基礎的特殊類別。

建構函式項目:

(array)

這個類別包含各種類似內部陣列的物件,但這些物件不會直接對應到 JavaScript 中顯示的物件。

舉例來說,JavaScript Array 物件的內容會儲存在名為 (object elements)[] 的次要內部物件中,以便輕鬆調整大小。同樣地,JavaScript 物件中的具名屬性通常儲存在名為 (object properties)[] 的次要內部物件中,這些物件也會列在 (array) 類別中。

(compiled code)

這個類別包含 V8 所需的內部資料,以便執行由 JavaScript 或 WebAssembly 定義的函式。每個函式都能以各種方式呈現,包括小、慢、大型和快速。

V8 會自動管理這個類別的記憶體用量。如果函式多次執行,V8 會為該函式使用更多記憶體,加快執行速度。如果函式已有一段時間未執行,V8 可能會清除該函式的內部資料。

(concatenated string)

V8 串連兩個字串 (例如使用 JavaScript + 運算子) 時,可以選擇在內部將結果表示為「串連字串」(也稱為 Rope 資料結構)。

V8 不會將兩個來源字串的所有字元複製到新字串,而是會分配一個包含 firstsecond 內部欄位的小物件,這些欄位指向兩個來源字串。讓 V8 節省時間和記憶體。從 JavaScript 程式碼的角度來看,這些只是一般字串,其行為就像任何其他字串。

InternalNode

這個類別代表在 V8 之外配置的物件,例如 Blink 定義的 C++ 物件。

如要查看 C++ 類別名稱,請使用 Chrome for Testing,並執行以下操作:

  1. 開啟開發人員工具,然後依序開啟設定「設定」 >「實驗」 >「check_box「顯示在堆積快照中顯示內部內部資料的選項」。
  2. 開啟「記憶體」面板,選取 radio_button_checked 的「堆積快照」,然後開啟 radio_button_checked 「揭露內部資料 (包含其他實作專屬詳情)」
  3. 重現導致 InternalNode 保留大量記憶體的問題。
  4. 拍攝堆積快照。在這個快照中,物件使用的是 C++ 類別名稱,而非 InternalNode
(object shape)

V8 中的快速屬性所述,V8 會追蹤「隱藏類別」 (或「形狀」),讓具有相同順序的多個物件能以高效率表示。這個類別包含這些隱藏類別,名為 system / Map (與 JavaScript Map 無關) 以及相關資料。

(sliced string)

V8 需要使用子字串時 (例如 JavaScript 程式碼呼叫 String.prototype.substring() 時),V8 可能會選擇分配切割字串物件,而不是複製原始字串中的所有相關字元。這個新物件含有原始字串的指標,用來說明原本要使用的字元範圍。

從 JavaScript 程式碼的角度來看,這些只是一般字串,其行為就像任何其他字串。如果切片字串保留大量記憶體,則程式可能會觸發 Issue 2869,對分割字串而言,採取謹慎的步驟或許會有幫助。

system / Context

system / Context 類型的內部物件含有「closure」的本機變數,該元件是巢狀函式可存取的 JavaScript 範圍。

每個函式執行個體都包含執行 Context 的內部指標,以便存取這些變數。雖然 JavaScript 不會直接顯示 Context 物件,但您還是可以直接控制這些物件。

(system)

這個類別包含尚未經過 (尚未) 分類,且尚未以任何更有意義的方式分類的內部物件。

比較資料檢視

「比較」檢視畫面會比較多個快照來找出外洩的物件。舉例來說,在執行或將動作 (例如開啟及關閉文件) 時,不應留下多餘的物件。

如要確認特定作業不會造成流失問題,請按照以下步驟操作:

  1. 在執行作業前拍攝堆積快照。
  2. 執行作業。也就是說,當您與網頁互動時,可能讓網頁發生資訊外洩的情形。
  3. 執行反向作業。也就是相反的互動,並重複幾次。
  4. 拍攝第二個堆積快照,並將其檢視畫面變更為「Comparison」(比較),並將其與「Snapshot 1」(快照 1) 進行比較。

「Comparison」(比較) 檢視畫面會顯示兩個快照之間的差異。展開總項目時,會顯示新增和刪除的物件例項:

與快照 1 相比。

包含項目檢視畫面

「容器」檢視畫面會列出應用程式物件結構的「鳥瞰圖」。這項工具可讓您搶先瞭解函式關閉期間、觀察組成 JavaScript 物件的 VM 內部物件,以及在極低的層級上瞭解應用程式的記憶體用量。

檢視畫面提供多個進入點:

  • DOMWindow 物件。JavaScript 程式碼的全域物件。
  • GC 根層級。VM 垃圾收集器使用的 GC 根。GC 根層級可包含內建物件對應、符號表、VM 執行緒堆疊、編譯快取、處理範圍和全域控制代碼。
  • 原生物件。瀏覽器物件「推送」在 JavaScript 虛擬機器中,以允許自動化作業,例如 DOM 節點和 CSS 規則。

「遏制」檢視畫面。

「協定」部分

「Memory」面板底部的「Retainers」部分會顯示指向檢視畫面中所選物件的物件。在「統計資料」以外的任何檢視畫面中選取不同物件時,「記憶體」面板會更新「保留工具」部分。

「協定」部分。

在這個範例中,Item 執行個體的 x 屬性會保留所選字串。

忽略保留器

您可以隱藏保留器,藉此找出其他任何物件。如果採用這個選項,您就不必先從程式碼中移除這個保留器,然後重新拍攝堆積快照。

下拉式選單中的「忽略這個保留器」選項。

如要隱藏保留器,請按一下滑鼠右鍵,然後選取「Ignore this 保留 er」(忽略這個保留器)。忽略的保留器在「Distance」欄中會標示為 ignored。如要停止忽略所有保留器,請在頂端的動作列中按一下 playlist_remove「還原已忽略的保留器」

尋找特定物件

如要在收集到的堆積中找出物件,可以使用 Ctrl + F 鍵進行搜尋,然後輸入物件 ID。

為函式命名,以便區分閉包

這有助於命名函式,以便在快照中區分閉包。

舉例來說,以下程式碼不使用已命名函式:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function() { // this is NOT a named function
    return largeStr;
  };

  return lC;
}

本範例雖有:

function createLargeClosure() {
  var largeStr = new Array(1000000).join('x');

  var lC = function lC() { // this IS a named function
    return largeStr;
  };

  return lC;
}

閉包中的已命名函式。

找出 DOM 流失問題

堆積分析器能夠反映瀏覽器原生物件 (DOM 節點和 CSS 規則) 和 JavaScript 物件之間的雙向依附關係。這有助於發現周圍因被遺忘的 DOM 子樹所造成的其他隱形流失。

DOM 流失可能比想像中大。請參考以下範例。「#tree」的垃圾收集時間為何?

  var select = document.querySelector;
  var treeRef = select("#tree");
  var leafRef = select("#leaf");
  var body = select("body");

  body.removeChild(treeRef);

  //#tree can't be GC yet due to treeRef
  treeRef = null;

  //#tree can't be GC yet due to indirect
  //reference from leafRef

  leafRef = null;
  //#NOW #tree can be garbage collected

#leaf 會維護對其父項 (parentNode) 的參照,並以遞迴方式向上 #tree,因此只有在 leafRef 為空值時,才是 #tree 候選項目的「整個」樹狀結構。

DOM 子樹狀結構