記憶體術語

梅金凱尼
Meggin Kearney

本節說明記憶體分析中使用的常用詞彙,適用於不同語言的各種記憶體剖析工具。

此處列出的條款和注意事項請參閱 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 字串內容。例如建構已彙整字串的子字串。

舉例來說,如果您串連 ab,就會得到代表串連結果的字串 (a, b)。如果之後將 d 與該結果串連,則會取得另一個 Cons 字串 ((a, b)、d)。

陣列 - 陣列是具有數字鍵的物件。在 V8 VM 中廣泛用於儲存大量資料。字典等使用鍵/值組合的組合則由陣列備份。

一般 JavaScript 物件可以是用於儲存的兩種陣列類型之一:

  • 已命名的屬性
  • 數值元素

如果屬性數量不多,則可儲存在 JavaScript 物件本身內部。

Map-一個物件,用於說明物件種類及其版面配置。例如,地圖可用來說明用於快速存取屬性的隱含物件階層。

物件群組

每個原生物件群組都由多個物件組成,這些物件各自包含彼此的參照。舉例來說,假設 DOM 子樹狀結構中每個節點都有一個父項和同層級連結的連結,並連結至下一個子項和下一個同層級,因此就會形成已連結的圖表。請注意,原生物件不會出現在 JavaScript 堆積中,因此其大小為零。而是建立包裝函式物件。

每個包裝函式物件都會保留對對應原生物件的參照,用於將指令重新導向到該物件。物件群組本身則保留包裝函式物件。不過,這不會建立無法收集的循環,因為 GC 很聰明,可以釋出不再參照包裝函式的物件群組。但如果忘記發布一個包裝函式,系統會保留整個群組和相關聯的包裝函式。