瞭解如何透過「記憶體」 >「設定檔」 >「堆積快照」記錄堆積快照,並找出記憶體外洩。
堆積分析器會根據網頁的 JavaScript 物件和相關 DOM 節點,顯示記憶體分布情形。您可以使用此工具擷取 JS 堆積快照、分析記憶體圖表、比較快照,以及找出記憶體流失問題。詳情請參閱「物件保留樹狀結構」。
拍攝快照
如何拍攝堆積快照:
- 在要分析的網頁上,開啟開發人員工具,然後前往「記憶體」面板。
- 選取「radio_button_checked」堆積快照剖析類型,然後選取 JavaScript VM 執行個體,然後按一下「Take snapshot」。
當「Memory」面板載入並剖析快照時,會在「HEAP SNAPSHOTS」部分的快照標題下方,顯示可到達的 JavaScript 物件總大小。
快照只會顯示記憶體圖中可從全域物件存取的物件。擷取快照時,系統一律會先進行垃圾收集。
清除快照
如要移除所有快照,請按一下「block」(封鎖所有快照):
查看快照
如要從不同角度檢視快照,以便用於不同用途,請從頂端的下拉式選單中選取其中一個檢視畫面:
查看 | 內容 | 目的 |
---|---|---|
摘要 | 依建構函式名稱分組的物件。 | 您可以使用這項工具,根據類型找出物件及其記憶體用量。有助於追蹤 DOM 流失情形。 |
比較 | 兩個快照之間的差異。 | 您可以使用此方法比較兩個 (或多個) 快照,比較作業前後的差異。檢查釋放記憶體和參照計數中的差異,確認記憶體流失的存在情形和原因。 |
容器化 | 堆積內容 | 可提供更佳的物件結構檢視畫面,並協助分析全域命名空間 (視窗) 中參照的物件,找出這些物件持續存在的原因。您可以用這個功能分析閉合情況,並在低階的情況下深入瞭解物件。 |
統計資料 | 記憶體配置圓餅圖 | 查看為程式碼、字串、JS 陣列、指定類型的陣列和系統物件分配的記憶體部分的相對大小。 |
摘要檢視
一開始,系統會在「Summary」檢視畫面中開啟堆積區快照,並在資料欄中列出「Constructors」。您可以展開建構函式,查看其例項化的物件。
如要篩除不相關的建構函式,請在「Summary」檢視畫面頂端的「Class filter」中,輸入要檢查的名稱。
建構函式名稱旁邊的數字表示使用建構函式建立的物件總數。「摘要」檢視畫面也會顯示下列資料欄:
- Distance 會顯示節點最短簡易路徑的根目錄距離。
- 「Shallow size」會顯示特定建構函式建立的所有物件淺層大小總和。淺層大小是指物件本身保留的記憶體大小。一般來說,陣列和字串的淺層大小較大。另請參閱「物件大小」。
- 「Retained size」會顯示同組物件中保留的最大大小。保留大小是指您可以透過刪除物件後,再也無法取得依附性而釋出的記憶體大小。另請參閱「物件大小」。
展開建構函式時,「Summary」(摘要) 檢視畫面會顯示該建構函式的所有執行個體。每個執行個體都會在對應的欄中,取得淺層和保留的大小細目。@
字元後面的數字是物件的專屬 ID。可讓您比較每個物件的堆積快照。
建構函式篩選器
Summary 檢視畫面可讓您根據記憶體使用效率不佳的常見情況篩選建構函式。
如要使用這些篩選器,請從操作列最右側的下拉式選單中選取下列其中一個選項:
- 所有物件:目前快照擷取的所有物件。預設為已設定。
- 快照 1 前配置的物件:在第一個快照拍攝之前,建立並保留在記憶體中的物件。
- 在快照 1 和快照 2 之間分配的物件:查看最新快照和上一個快照之間的物件差異。每新增一個快照,這個篩選器的累進時間就會增加到下拉式清單中。
- 重複的字串:在記憶體中多次儲存的字串值。
- 由已卸離的節點保留的物件:因為已卸離的 DOM 節點參照物件,而保留物件運作。
- 開發人員工具控制台保留的物件:透過開發人員工具控制台評估或互動的物件,因此會保留在記憶體中。
摘要中的特殊項目
除了依建構函式分組之外,Summary 檢視畫面也會依以下條件將物件分組:
- 內建函式,例如
Array
或Object
。 - 按照標記分組的 HTML 元素,例如
<div>
、<a>
、<img>
等。 - 您在程式碼中定義的函式。
- 不以建構函式為依據的特殊類別。
(array)
這個類別包含各種內部陣列類似物件,這些物件並未直接對應至 JavaScript 中顯示的物件。
舉例來說,JavaScript Array
物件的內容會儲存在名為 (object elements)[]
的次要內部物件中,方便您調整大小。同樣地,JavaScript 物件中的命名屬性通常會儲存在名為 (object properties)[]
的次要內部物件中,這些物件也會列在 (array)
類別中。
(compiled code)
這個類別包含 V8 需要的內部資料,以便執行 JavaScript 或 WebAssembly 定義的函式。每個函式都可以以多種方式表示,從小而慢到大而快。
V8 會自動管理這個類別的記憶體用量。如果函式執行多次,V8 會為該函式使用更多記憶體,以便加快執行速度。如果函式已一段時間未執行,V8 可能會清除該函式的內部資料。
(concatenated string)
當 V8 連結兩個字串 (例如使用 JavaScript +
運算子) 時,可能會選擇在內部將結果表示為「連接字串」,也稱為Rope 資料結構。
與其將兩個來源字串的所有字元複製到新字串,不如讓 V8 分配一個小型物件,其中包含名為 first
和 second
的內部欄位,指向兩個來源字串。這可讓 V8 節省時間和記憶體。從 JavaScript 程式碼的角度來看,這些只是一般字串,並且會像其他字串一樣運作。
InternalNode
這個類別代表在 V8 之外配置的物件,例如 Blink 定義的 C++ 物件。
如要查看 C++ 類別名稱,請使用 Chrome for Testing,並執行以下操作:
- 開啟開發人員工具,然後依序開啟「設定」>「實驗」>「顯示堆積快照中的內部選項」。
- 開啟「Memory」面板,選取「radio_button_checked」「Heap 快照」,然後開啟「check_box」「Expose internals (includes additional implementation-specific details)」。
- 重現導致
InternalNode
保留大量記憶體的問題。 - 拍攝堆積快照。在這張快照中,物件具有 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
的內部指標,以便存取這些變數。雖然 Context
物件無法直接從 JavaScript 中顯示,但您可以直接控制這些物件。
(system)
這個類別包含各種尚未以更有意義的方式分類的內部物件。
比較資料檢視
您可以使用「比較」檢視畫面,比較多個快照畫面,找出外洩的物件。舉例來說,在執行或將動作 (例如開啟及關閉文件) 時,不應留下多餘的物件。
如要確認特定作業不會造成流失問題,請按照以下步驟操作:
- 在執行作業前拍攝堆積快照。
- 執行作業。也就是說,以您認為可能會造成記憶體外洩的方式與網頁互動。
- 執行反向作業。也就是說,請執行相反的互動,並重複幾次。
- 拍攝第二個堆積快照,並將其檢視畫面變更為「比較」,與「快照 1」進行比較。
「比較」檢視畫面會顯示兩個快照畫面之間的差異。展開總計項目時,系統會顯示新增和刪除的物件例項:
遏制檢視
「Containment」檢視畫面是應用程式物件結構的「鳥瞰圖」。這項工具可讓您窺探函式結束動作,觀察組成 JavaScript 物件的 VM 內部物件,並瞭解應用程式在非常低層級使用的記憶體量。
這個檢視畫面提供多個進入點:
- DOMWindow 物件。JavaScript 程式碼的全域物件。
- GC 根層級。VM 垃圾收集器使用的 GC 根目錄。GC 根目錄可包含內建物件對應、符號表、VM 執行緒堆疊、編譯快取、句柄範圍和全域句柄。
- 原生物件:瀏覽器物件「推送」至 JavaScript 虛擬機器中,以便執行自動化作業,例如 DOM 節點和 CSS 規則。
「Retainers」區段
「Memory」面板底部的「Retainers」部分會顯示指向檢視畫面中所選物件的物件。在「統計資料」以外的任何檢視畫面中選取不同物件時,「記憶體」面板會更新「保留工具」部分。
在本例中,Item
例項的 x
屬性會保留所選字串。
忽略保留器
您可以隱藏保留者,找出其他物件是否保留所選保留者。使用這個選項時,您不必先從程式碼中移除此保留者,然後再重新擷取堆積快照。
如要隱藏保留者,請按一下滑鼠右鍵,然後選取「Ignore this retainer」。忽略的保留器在「Distance」欄中會標示為 ignored
。如要停止忽略所有保留器,請在頂端的動作列中按一下 「還原已忽略的保留器」。
尋找特定物件
如要在收集到的堆積中尋找物件,請使用 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
下的整個樹狀結構才會成為 GC 的候選項目。