記憶體術語

Meggin Kearney
Meggin Kearney

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

此處所述的詞彙和解釋請參閱 Chrome 開發人員工具堆積分析器。如果您曾使用 Java、.NET 或其他記憶體分析器,可能就需要複習一下。

物件大小

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

記憶體的示意圖

物件保留記憶體的方式有兩種:

  • 直接從物件本身。
  • 以隱含的方式保留其他物件的參照,進而防止垃圾收集器自動處理這些物件 (簡稱為 GC)。

使用開發人員工具中的堆積分析器 (用於調查「Profiles」下方記憶體問題的工具) 時,您可能會注意到一些不同的資訊欄。「淺層大小」和「保留大小」這兩個值得注意的兩個版本,但這兩者之間有何差異?

淺層大小和保留大小

淺層大小

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

一般 JavaScript 物件會保留一些記憶體,以供其說明及儲存即時值。通常只有陣列和字串才能有相當高的淺層大小。不過,字串和外部陣列通常在轉譯器記憶體中有主要儲存空間,只會在 JavaScript 堆積上公開小型的包裝函式物件。

轉譯器記憶體是指轉譯已檢查頁面之程序的所有記憶體,包括該頁面的原生記憶體 + JS 堆積記憶體 + 在該頁面啟動的所有專屬工作站的 JS 堆積記憶體。然而,即使是小物件也能間接保存大量記憶體,方法是透過自動垃圾收集程序防止其他物件處置。

保留大小

這是在物件本身刪除後釋出的記憶體大小,以及從 GC 根層級無法存取的相依物件。

GC 根層級是由帳號代碼所組成的,是指從原生程式碼編寫至 V8 以外 JavaScript 物件時所建立的 (本機或全域)。您可以在「GC roots」 >「Handle scope」和「GC roots」 >「Global 控制」下方的堆積快照中找到所有這類控點。如果未詳細瞭解瀏覽器實作細節,可能會混淆說明文件中的控點。您無需擔心 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 的主因,但 #7 也存在於從 GC 到 #10 的每個簡單路徑中。因此,如果從根到物件 A 的每個簡單路徑中都有物件 B,物件 B 就是物件 A 的主因。

主角的動畫插圖

V8 規格

分析記憶體時,瞭解堆積快照為何會以特定方式呈現,這樣很有幫助。本節說明特別對應 V8 JavaScript 虛擬機器 (V8 VM 或 VM) 的一些記憶體相關主題。

JavaScript 物件表示法

原始類型有三種:

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

而且無法參照其他值,而且一定是分葉或終止節點。

Numbers 可儲存為下列其中一項:

  • 為直效 31 位元整數值,稱為小整數 (SMI),或
  • 堆積物件,稱為「堆積編號」。堆積數字可用來儲存不符合 SMI 形式的值,例如 doubles,或是需要「加上黑邊」的值,例如設定其屬性時。

字串可儲存在下列任一方式中:

  • 「VM heap」
  • 轉譯器記憶體中的外部 IP 位址。建立並用來存取外部儲存空間 (例如從網路接收的指令碼來源和其他內容),而不是複製到 VM 堆積上。

新 JavaScript 物件的記憶體是由專屬的 JavaScript 堆積 (或 VM 堆積) 分配。這些物件是由 V8 的垃圾收集器所管理,因此只要對這些物件至少有一項強烈參照,這些物件就會保持有效狀態。

原生物件是 JavaScript 堆積以外的所有其他項目。原生物件相對於堆積物件,在整個生命週期中都是由 V8 垃圾收集器管理,且只能透過 JavaScript 包裝函式物件透過 JavaScript 存取。

「Cons string」是一個物件,由一組之後儲存的字串組合組成,為串連結果。系統只會視需要彙整字串字串內容。例如需要建構已彙整字串的子字串時。

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

陣列:陣列是含有數字索引鍵的物件。V8 VM 廣泛使用它們來儲存大量資料。字典會使用各種鍵/值組合 (例如字典) 備份。

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

  • 命名屬性
  • 數值元素

如果屬性數量很少,可以儲存在 JavaScript 物件本身內部。

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

物件群組

每個原生物件群組都是由物件組成,而物件之間會保留彼此的相互參照。例如,一個 DOM 子樹狀結構,其中每個節點都有其父項的連結,並連結至下一個子項和下一個同層,因此形成連結的圖表。請注意,由於原生物件不會在 JavaScript 堆積中顯示,所以大小為零。而是會建立包裝函式物件。

每個包裝函式物件會保留對應原生物件的參照,用來將指令重新導向至該物件。物件群組會自行保存包裝函式物件。不過,這種做法不會產生無法收集的週期,因為 GC 很聰明,可以釋放包裝函式不再參照的物件群組。但忘記釋放單一包裝函式,會保留整個群組和相關包裝函式。