記憶體術語

Meggin Kearney
Meggin Kearney

本節說明記憶體分析中使用的常見術語,適用於各種語言的各種記憶體分析工具。

本文所述的術語和概念均是指 Chrome 開發人員工具堆積分析器。如果您曾使用過 Java、.NET 或其他記憶體分析器,這篇文章可能會讓您回顧相關知識。

物件大小

請將記憶體視為包含原始類型 (例如數字和字串) 和物件 (關聯陣列) 的圖表。這可能會以圖表呈現,其中包含多個相互連結的點,如下所示:

記憶體的視覺化呈現方式。

物件可透過兩種方式儲存記憶體:

  • 直接由物件本身。
  • 隱含地保留其他物件的參照,因此可防止垃圾收集器 (簡稱 GC) 自動處置這些物件。

在 DevTools 中使用堆積分析器 (用於調查「記憶體」面板中所發現的記憶體問題的工具) 時,您可能會看到幾個不同的資訊欄。其中兩個值得注意的值是「Shallow Size」和「Retained Size」,但這兩個值代表什麼?

「Memory」面板中的「Shallow」和「Retained Size」欄。

淺層大小

這是物件本身所保留的記憶體大小。

一般 JavaScript 物件會保留一些記憶體,用於說明和儲存即時值。通常只有陣列和字串可以有顯著的淺層大小。不過,字串和外部陣列通常會將主要儲存空間放在轉譯器記憶體中,只在 JavaScript 堆疊上公開一個小型包裝函式物件。

轉譯器記憶體是轉譯檢查頁面的程序所有記憶體:原生記憶體 + 頁面的 JS 堆積記憶體 + 頁面啟動的所有專屬工作站的 JS 堆積記憶體。不過,即使是小型物件,也可以間接保留大量記憶體,因為這樣可防止自動垃圾收集程序處理其他物件。

保留大小

這是物件本身和其無法從 GC 根存取的依附物件遭到刪除後,釋出的記憶體大小。

GC 根是由句柄組成,這些句柄是在從原生程式碼參照 V8 以外的 JavaScript 物件時建立 (本機或全域)。所有這類句柄都可以在堆積區快照的 GC 根 > 句柄範圍GC 根 > 全域句柄 下方找到。在本說明文件中說明句柄,而不深入探討瀏覽器實作方式的細節,可能會造成混淆。GC 根目錄和句柄都不是您需要擔心的東西。

內部 GC 根目錄很多,但大多數都不是使用者感興趣的內容。從應用程式角度來看,根有以下幾種:

  • Window 全域物件 (位於每個 iframe 中)。堆積集快照中含有距離欄位,這是從視窗中找到最短保留路徑的屬性參照數量。
  • 文件 DOM 樹狀結構,包含可透過遍歷文件存取的所有原生 DOM 節點。並非所有元素都會有 JS 包裝函式,但如果有,則包裝函式會在文件運作期間保持運作。
  • 有時物件可能會由偵錯工具背景和開發人員工具控制台保留 (例如在控制台評估後)。在偵錯工具中,使用清除主控台且沒有有效中斷點的堆積快照。

記憶體圖表會從根節點開始,該節點可能是瀏覽器的 window 物件,或 Node.js 模組的 Global 物件。您無法控制這個根物件的 GC 方式。

無法控制根物件。

從根目錄無法存取的任何項目都會遭到 GC。

物件保留樹狀結構

堆積是連結在一起的物件網路。在數學領域中,這種結構稱為「圖表」或「記憶體圖表」。圖表是由節點邊緣所組成,兩者都會指定標籤。

  • 節點 (或物件) 會使用用於建構節點的建構函式名稱進行標記。
  • 會使用屬性的名稱標示。

瞭解如何使用堆積分析工具記錄設定檔。在下列堆積分析器記錄中,我們可以看到一些引人注目的內容,包括距離:距離 GC 根目錄的距離。如果幾乎所有相同類型的物件都位於相同距離,而少數物件位於更遠的距離,那麼這項情況就值得調查。

距離根目錄的範例。

Dominators

由於每個物件都只有一個主導者,因此主導者物件由樹狀結構組成。物件的支配項可能缺少對其支配物件的直接參照,也就是說,支配項的樹狀圖並非圖的覆蓋樹狀圖。

在下圖中:

  • 節點 1 主宰節點 2
  • 節點 2 支配節點 3、4 和 6
  • 節點 3 主宰節點 5
  • 節點 5 主控節點 8
  • 節點 6 主宰節點 7

支配項樹狀結構。

在以下範例中,節點 #3#10 的支配節點,但 #7 也存在於從 GC 到 #10 的每個簡易路徑中。因此,如果物件 B 存在於從根目錄到物件 A 的每個簡單路徑中,則物件 B 是物件 A 的支配者。

動畫版的霸凌插圖。

V8 特定內容

剖析記憶體時,瞭解堆積快照為何會呈現某種樣貌,有助於您進行剖析。本節將說明一些與記憶體相關的主題,這些主題特別對應至 V8 JavaScript 虛擬機器 (V8 VM 或 VM)。

JavaScript 物件表示法

原始類型有三種:

  • 數字 (例如3.14159..)
  • 布林值 (true 或 false)
  • 字串 (例如「Werner Heisenberg」)

這些節點無法參照其他值,且一律為葉節點或終端節點。

數字可以儲存為下列格式:

  • 立即 31 位元整數值,稱為「小整數」 (SMIs),或
  • 堆積物件,稱為堆積數。堆積數字可用於儲存不符合 SMI 格式的值,例如雙精度數,或是當值需要封箱時,例如設定屬性。

字串可以儲存在下列任一位置:

  • VM 堆積,或
  • 轉譯器的記憶體中外部儲存。系統會建立包裝函式物件,並用於存取外部儲存空間,例如從網路接收的腳本來源和其他內容,而非複製到 VM 堆積上。

系統會從專屬的 JavaScript 堆積 (或VM 堆積) 配置新 JavaScript 物件的記憶體。這些物件由 V8 的垃圾收集器管理,因此只要有至少一個強烈參照,這些物件就會持續運作。

原生物件是指不在 JavaScript 堆積中的所有物件。與堆積物件相反,原生物件在整個生命週期中不會受到 V8 垃圾收集器的管理,而且只能透過 JavaScript 使用其 JavaScript 包裝函式物件存取。

Cons 字串是組合後儲存的字串組合物件,也是連接的結果。cons string 內容的彙整作業只會在需要時執行。舉例來說,如果需要建構已彙整字串的子字串,就會發生這種情況。

舉例來說,如果您連接 ab,您會得到字串 (a, b),代表連接的結果。如果您稍後將 d 連結至該結果,您會得到另一個連結字串 ((a, b), d)。

陣列:陣列是含有數字鍵的物件。這些類別廣泛用於 V8 VM 中,用於儲存大量資料。用於字典的鍵/值組合集會由陣列備份。

一般 JavaScript 物件可分為以下兩種陣列類型,用於儲存資料:

  • 命名屬性,以及
  • 數字元素

如果屬性數量很少,則可將這些屬性儲存在 JavaScript 物件本身的內部。

Map:用於說明物件類型及其版面配置的物件。舉例來說,圖可用於描述隱含物件階層,以便快速存取屬性

物件群組

每個原生物件群組都由彼此持有彼此參照的物件組成。舉例來說,DOM 子樹狀結構中的每個節點都有一個連結至其父項的連結,以及連結至下一個子項和下一個同胞節點,因此形成連結圖表。請注意,原生物件不會顯示在 JavaScript 堆積中,因此大小為零。而是建立包裝函式物件。

每個包裝函式物件都會保留對應原生物件的參照,以便將指令重新導向至該物件。在自身的輪替中,物件群組會保留包裝函式物件。不過,這不會造成無法收集的循環,因為 GC 會聰明地釋放已不再參照包裝函式的物件群組。但如果忘記釋放單一包裝函式,整個群組和相關的包裝函式都會保留。