本節說明記憶體分析中使用的常用詞彙,適用於不同語言的各種記憶體剖析工具。
此處列出的條款和注意事項請參閱 Chrome 開發人員工具堆積分析器。如果您曾經使用 Java、.NET 或其他記憶體分析器,這可能是一項回顧。
物件大小
您可以將記憶體視為原始類型 (例如數字和字串) 和物件 (關聯陣列) 的圖形。該圖示可能以圖表呈現,並包含多個互連點,如下所示:
物件可透過兩種方式保留記憶體:
- 直接由物件本身。
- 隱含參照其他物件的參照,從而防止垃圾收集器 (簡稱 GC) 自動丟棄這些物件。
使用開發人員工具中的堆積分析器 (用來調查「Profiles」下方發現記憶體問題的工具) 時,可能會看到幾欄資訊。其中兩個分別是「Shallow Size」(淺尺寸) 和「Retained Size」(保留大小),但這兩者代表了什麼?
淺層大小
這是物件本身所保留的記憶體大小。
一般 JavaScript 物件會預留一些記憶體做為其說明及儲存立即值使用。一般來說,只有陣列和字串可以有明顯的淺層大小。不過,字串和外部陣列通常在轉譯器記憶體中都有主要儲存空間,只會公開 JavaScript 堆積上的小型包裝函式物件。
轉譯器記憶體是指轉譯已檢查網頁的所有程序記憶體:網頁的原生記憶體 + JS 堆積記憶體 + 頁面啟動的所有專用工作站的 JS 堆積記憶體。儘管如此,即使是小型物件,也能間接容納大量記憶體,方法是防止自動垃圾收集程序處理其他物件。
保留大小
這是物件本身遭到刪除後釋出的記憶體大小,以及無法透過 GC 根層級存取的相依物件。
GC 根由在從原生程式碼參照 V8 以外的 JavaScript 物件時建立的帳號代碼組成 (本機或全域)。只要前往「GC 根」 >「處理範圍」和「GC 根目錄」 >「全域控制代碼」,即可在堆積快照中找到所有這類控點。描述本文中的控制代碼時,如果未深入深入瞭解瀏覽器實作內容,可能會令人感到困惑。您不必擔心 GC 根層級和控點。
有許多內部 GC 根源,但使用者大多並不感興趣。從應用程式的角度來看,有幾個根源:
- 視窗全域物件 (在每個 iframe 中)。堆積快照中有距離欄位,也就是來自視窗最短保留路徑的屬性參照數量。
- 文件 DOM 樹狀結構,包含可透過掃遍文件到達的所有原生 DOM 節點。並非全部都有 JS 包裝函式,但如果這些包裝函式在文件生效時仍存在,則這些包裝函式將會生效。
- 有時候,偵錯工具結構定義和開發人員工具控制台可能會保留物件,例如在控制台評估後保留物件。使用明確的主控台建立堆積快照,且偵錯工具中沒有任何有效的中斷點。
記憶體圖表以根層級開頭,可能是瀏覽器的 window
物件或 Node.js 模組的 Global
物件。您無法控制這個根物件 GC 的方式。
不論根源無法連線的項目都會得到 GC。
保留樹狀結構的物件
堆積是互連物件的網路。在數學世界中,這個結構稱為圖形或記憶體圖表。圖表建構於透過「邊緣」功能連接的「節點」,兩者均為指定標籤。
- 節點 (或物件) 會以用來建構這些節點的「建構函式」函式名稱加上標籤。
- 邊緣會使用屬性名稱加上標籤。
瞭解如何使用堆積分析器記錄設定檔。我們在下方的堆積分析器記錄中發現一些值得注意的功能,包括距離:與 GC 根目錄之間的距離。如果同類型的所有物件幾乎都具有相同的距離,有些物件的距離也很遠,您就要調查這個問題。
統整人員
主角物件是由樹狀結構所組成,因為每個物件都只有一個主視窗。物件的核心可能沒有直接參照其主物件;也就是說,主角的樹狀結構不是橫跨圖表的樹狀結構。
在下圖中:
- 節點 1 佔據節點 2
- 節點 2 佔據節點 3、4 和 6
- 節點 3 佔據節點 5
- 節點 5 佔據節點 8
- 節點 6 佔據節點 7
在以下範例中,節點 #3
是 #10
的主節點,但從 GC 到 #10
的每個簡單路徑中,#7
也都存在。因此,如果從根層級到物件 A 的所有簡單路徑中都存在 B 物件,則物件 B 是物件 A 的主導者。
V8 特定
分析記憶體時,建議您瞭解堆積快照以特定方式呈現的原因。本節說明一些與 V8 JavaScript 虛擬機器 (V8 VM 或 VM) 明確對應的記憶體相關主題。
JavaScript 物件表示法
原始類型可分為三種:
- 數字 (例如3.14159.)
- Booleans (true 或 false)
- 字串 (例如「Werner Heisenberg」)
它們無法參照其他值,也一律是分葉或終止節點。
數字可以儲存為以下其中一種方式:
- 即時 31 位元整數值,稱為小整數 (SMI),或
- 堆積物件,稱為「堆積數」。堆積號碼可用來儲存不符合 SMI 形式的值 (如「雙倍數」),或需要「黑邊」的值 (例如設定其屬性)。
字串可儲存在以下其中一種方式:
- VM 堆積,或
- 轉譯器記憶體從外部取得。系統會建立包裝函式物件並用於存取外部儲存空間,例如儲存從網路接收的指令碼來源和其他內容,而非複製到 VM 堆積。
系統會從專屬的 JavaScript 堆積 (或 VM 堆積) 分配新 JavaScript 物件的記憶體。這些物件是由 V8 的垃圾收集器管理,因此只要有至少一個明確參照,這些物件就會保持運作。
原生物件是指 JavaScript 堆積中的所有其他物件。原生物件 (相對於堆積物件) 並非由 V8 垃圾收集器在生命週期內管理,且只能透過 JavaScript 包裝函式物件透過 JavaScript 存取。
Cons 字串是一個物件,由儲存然後彙整的字串組合組成,也是串連結果。只有在需要時,才能彙整 cons 字串內容。例如建構已彙整字串的子字串。
舉例來說,如果您串連 a 和 b,就會得到代表串連結果的字串 (a, b)。如果之後將 d 與該結果串連,則會取得另一個 Cons 字串 ((a, b)、d)。
陣列 - 陣列是具有數字鍵的物件。在 V8 VM 中廣泛用於儲存大量資料。字典等使用鍵/值組合的組合則由陣列備份。
一般 JavaScript 物件可以是用於儲存的兩種陣列類型之一:
- 已命名的屬性
- 數值元素
如果屬性數量不多,則可儲存在 JavaScript 物件本身內部。
Map-一個物件,用於說明物件種類及其版面配置。例如,地圖可用來說明用於快速存取屬性的隱含物件階層。
物件群組
每個原生物件群組都由多個物件組成,這些物件各自包含彼此的參照。舉例來說,假設 DOM 子樹狀結構中每個節點都有一個父項和同層級連結的連結,並連結至下一個子項和下一個同層級,因此就會形成已連結的圖表。請注意,原生物件不會出現在 JavaScript 堆積中,因此其大小為零。而是建立包裝函式物件。
每個包裝函式物件都會保留對對應原生物件的參照,用於將指令重新導向到該物件。物件群組本身則保留包裝函式物件。不過,這不會建立無法收集的循環,因為 GC 很聰明,可以釋出不再參照包裝函式的物件群組。但如果忘記發布一個包裝函式,系統會保留整個群組和相關聯的包裝函式。