首頁  >  文章  >  web前端  >  Chrome開發者工具之JavaScript記憶體分析的圖文介紹

Chrome開發者工具之JavaScript記憶體分析的圖文介紹

黄舟
黄舟原創
2017-03-14 15:28:102210瀏覽

記憶體洩漏是指電腦可用記憶體的逐漸減少。當程式持續無法釋放其使用的臨時記憶體時就會發生。 JavaScript的web應用程式也會經常遇到在原生應用程式中出現的記憶體相關的問題,如洩漏和溢出,web應用也需要處理垃圾回收停頓

儘管JavaScript使用垃圾回收進行自動記憶體管理,但有效的(effective)記憶體管理依然很重要。在這篇文章中我們將探討分析JavaScript web應用程式中的記憶體問題。在學習有關特性時請確保嘗試相關案例以提高你對這些工具在實踐中如何運作的認識。請閱讀記憶體 101(Memory 101)頁面來幫助你熟悉這篇文章中使用的術語。 注意:我們將要用到的某些特性目前僅對Chrome Canary版瀏覽器可用。我們推薦使用這個版本來獲得最佳的工具,以分析你的應用程式的記憶體問題。

你需要思考的問題

整體來說,當你覺得你遇到了記憶體洩漏問題時,你需要思考三個問題:

術語和

基本概念

#本小節介紹在

記憶體分析時使用的常用術語,這些術語在為其它語言做記憶體分析的工具中也適用。這裡的術語和概念用在了堆分析儀(Heap Profiler)UI工具和相關的文件中。

這些能夠幫助我們熟悉如何有效的使用記憶體分析工具。如果你曾經用過像Java、.NET等語言的記憶體分析工具的話,那麼這將會是一個

複習

物件大小(Object sizes)

把記憶體想像成一個包含基本型別(像數字和

字串)和物件(關聯陣列 )的圖表。它可能看起來像是下面這幅一系列相關聯的點組成的圖。

一個物件有兩種使用記憶體的方法:

  • #物件本身直接使用

  • 隱含的保持對其它物件的引用,這種方式會阻止垃圾回收(簡稱GC)對那些物件的自動回收處理。

當你使用DevTools中的堆分析儀(Heap Profiler,用來分析記憶體問題的工具,在DevTools的」Profile”標籤下)時,你可能會驚訝的發現一些顯示各種資訊的欄位。其中有兩項是:

直接佔用記憶體(Shallow Size)佔用總記憶體(Retained Size),那它們是什麼意思呢?

直接佔用記憶體(Shallow Size,不包括引用的物件所佔用的記憶體)

這個是物件本身所佔用的記憶體。

典型的JavaScript物件都會有保留記憶體用來描述這個物件和儲存它的直接值。一般,只有陣列和字串會有明顯的直接佔用記憶體(Shallow Size)。但字串和陣列常常會在渲染器記憶體中儲存主要資料部分,僅在JavaScript物件堆疊中暴露一個很小的包裝物件。

渲染器記憶體是指你分析的頁面在渲染的過程中所用到的所有記憶體:頁面本身的記憶體+ 頁面中的JS堆疊到的記憶體+ 頁面觸發的相關工作行程(workers)中的JS堆用到的記憶體。然而,透過阻止垃圾自動回收別的對象,一個小對像都有可能間接佔用大量的記憶體。

佔用總記憶體(Retained Size,包括引用的物件所佔用的記憶體)

一個物件一但刪除後面它所引用的依賴物件就不能被GC根(GC root)引用到,它們所佔用的記憶體就會被釋放,一個物件佔用總記憶體包括這些依賴物件所佔用的記憶體。

GC根是由控制器(han#dles)組成的,這些控制器(不論是局部還是全域)是在建立由build-in函數(native code)到V8引擎以外的JavaScript物件的參考時創建的。所有這些控制器都能夠在堆疊快照的GC roots(GC根) > Handle scope 和 GC roots >Global handlers中找到。如果不深入了解瀏覽器的實作原理,在這篇文章中介紹這些控制器可能會讓人無法理解。 GC根和控制器你都不需要太在意。

有很多內部的GC根對使用者來說都是不重要的。從應用的角度來說有以下幾種情況:

  • Window 全域物件 (所有iframe中的)。在堆快照中有一個distance字段,它是從window物件到達對應物件的最短路徑長度。

  • 由所有document能夠遍歷到的DOM節點組成的文檔DOM樹。不是所有節點都會被對應的JS引用,但有JS引用的節點在document存在的情況下都會被保留。

  • 有很多物件可能是在調試程式碼時或DevTools console中(例如:console中的一些程式碼執行結束後)創建出來的。

注意:我們推薦使用者在建立堆疊快照時,不要在console中執行程式碼,也不要啟用偵錯斷點。

記憶體圖由一個根部開始,可能是瀏覽器的window物件或Node.js模組Global物件。這些物件如何被記憶體回收不受使用者的控制。

不能被GC根遍歷到的物件都會被記憶體回收。

注意:直接佔用記憶體和佔用總記憶體欄位中的資料是用位元組表示的。

物件的佔用總記憶體樹

之前我們已經了解到,堆是由各種互相關聯的物件組成的網狀結構。在數字領域,這種結構被稱為或記憶體圖。圖是由邊緣(edges)連接的節點(nodes)組成的,他們都被貼了標籤。

  • 節點(Nodes) (或物件) 節點的標籤名稱是由建立他們的建構(constructor)函數的名稱確定

  • 邊緣(Edges) 標籤名稱就是屬性名稱

#####################本文檔的後面你將會了解如何使用堆分析儀產生快照。從下圖的堆分析儀產生的快照中,我們可以看到距離(distance)這個欄位:是指物件到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物件描述

有三個原始類型:

  • #數字(Numbers) (如3.14159..)

  • ##布林值(Booleans) (true或false)字元型(Strings) (如'Werner Heisenberg')

  • #它們不會引用別的值,它們只會是葉子節點或終止節點。

  • 數字(Numbers)

    以下面兩種方式之一被儲存:

  • 31位元
整數

直接值,稱做:小整數(small integer

s)

(SMIs),或

##堆對象,引用為

堆值。堆值是用來儲存不適合用SMI形式儲存的數據,像雙精度數(doubles),或是當一個值需要被打包(boxed)

時,如給這個值再設定屬性值。

字元型資料會以下列兩種方式儲存:

VM堆,或

外部的
    渲染器記憶體
  • 中。這時會建立一個包裝物件用來存取儲存的位置,例如,Web頁麵包存的腳本資源和其它內容,而不是直接複製到VM堆中。

  • 新建立的JavaScript物件會被在JavaScript堆疊上(或

    VM堆疊

    )分配記憶體。這些物件由V8的垃圾回收器管理,只要還有一個強引用他們就會在記憶體中保留。

本地物件

是所有不在JavaScript堆中的對象,與堆疊物件不同的是,在它們的

生命週期中,不會被V8垃圾加收器處理,只能透過JavaScript包裝物件引用。 連接字串

是由一對字串合併成的對象,是合併後的結果。

連接字串

只在有需要時合併。像一連接字串的子字串需要被建構時。

例如:如果你連接###a###和###b###,你得到字串(a, b)這用來表示連接的結果。如果你之後要再把這個結果與###d###連接,你就得到了另一個連接字串((a, b), d)。 #########陣列(###Array###s)### - 陣列是數字類型鍵的物件。它們在V8引擎中儲存大數據量的資料時被廣泛的使用。像字典這種有鍵-值對的物件就是用陣列實現的。 ######一個典型的JavaScript物件可以透過兩種陣列類型之一的方式來儲存:#############命名屬性,和########## ##數位化的元素############如果只有少量的屬性,它們會直接儲存在JavaScript物件本身中。 ############Map###### - 一種用來描述物件類型和它的結構的物件。例如,maps會被用來描述物件的結構以實現對物件屬性的快速存取######物件群組######每個本地物件群組都是由一組之間相互關聯的物件組成的。例如一個DOM子樹,每個節點都能存取它的父元素,下一個子元素和下一個兄弟元素,它們構成了關聯圖。需要注意的是本地元素沒有在JavaScript堆中表現——這就是它們的大小是零的原因,而它的包裝物件被創建了。 ###

每個包裝物件都會有一個到本地物件的引用,用來傳遞對這些本地物件的操作。這些本地物件也有到包裝物件的參考。但這並不會創造無法收回的循環,GC是足夠聰明的,能夠分辨出那些已經沒有引用包裝物件的本地物件並釋放它們的。但如果有一個包裝物件沒有被釋放那它將會保留所有物件群組和相關的包裝物件。

先決條件與有用提示

Chrome 任務管理器

注意: 當使用Chrome做記憶體分析時,最好設定一個潔淨的測試環境

打開Chrome的記憶體管理器,觀察記憶體字段,在一個頁面上做相關的操作,你可以很快定位這個操作是否會導致頁面佔用很多記憶體。你可以從Chrome選單 > 工具或按Shift + Esc,找到記憶體管理器。

開啟後,在標頭右鍵選取 JavasScript使用的記憶體 這項。

透過DevTools Timeline來定位記憶體問題

解決問題的第一步就是要能夠證明問題存在。這就需要建立一個可重現的測試來做為問題的基準量測。沒有可重現的程序,就不能可靠的度量問題。換句話說如果沒有基準來做對比,就無法知道是哪些改變使問題出現的。

時間軸面版(Timeline panel)對於發現程式何時出了問題很用幫助。它展示了你的web應用程式或網站加載和互動的時刻。所有的事件:從載入資源到解JavaScript,樣式計算,垃圾回收停頓和頁面重繪。都在時間軸上表示出來了。

當分析記憶體問題時,時間軸面版上的記憶體檢視(Memory view)能用來觀察:

  • 使用的總記憶體– 記憶體使用成長了什麼?

  • DOM節點數

  • ##文件(documents)數

  • 註冊的事件監聽器(event listeners)數

#更多的關於在記憶體分析時,定位內存洩漏的方法,請閱Zack Grossbart的Memory profiling with the Chrome DevTools

證明一個問題的存在

首先要做的事情是找出你認為可能導致內存洩漏的一些動作。可以是發生在頁面上的任何事件,滑鼠移入,點擊,或其它可能會導致頁面效能下降的互動。

在時間軸面版上開始記錄(Ctrl+E 或 Cmd+E)然後做你想要測試的動作。想要強制進行垃圾回收點面版上的垃圾筒圖示(

)。

下面是一個記憶體洩漏的例子,有些點沒有被垃圾回收:

#如果經過一些反覆測試後,你看到的是鋸齒狀的圖形(在記憶體面版的上方),說明你的程式中有很多短時存在的物件。而如果一系列的動作沒有讓記憶體保持在一定的範圍,而DOM節點數沒有回到開始時的數目,你就可以懷疑有記憶體洩漏了。

一旦確定了記憶體上存在的問題,你就可以使用

分析面板(Profiles panel)上的堆分析儀(heap profiler)來定位問題的來源。

範例: 試試memory growth的例子,能幫助你有效的

練習透過時間軸分析記憶體問題。

記憶體回收

記憶體回收器(像V8中的)需要能夠定位哪些物件是活的(live),而那些被認為是死的(垃圾)的物件是無法引用到的(unreachable)

如果

垃圾回收 (GC)因為JavaScript執行時有邏輯錯誤而沒有能夠回收到垃圾對象,這些垃圾對象就無法再被重新回收了。像這樣的情況最終會讓你的應用越來越慢。

例如你在寫程式碼時,有的

變數和事件監聽器已經用不到了,但是卻還是被有些程式碼引用。只要引用還存在,那被引用的物件就無法被GC正確的回收。

當你的應用程式在運行中,有些DOM物件可能已經更新/移除了,要記住檢查引用了DOM物件的變數並將其設定null。檢查可能會引用到其它物件(或其它DOM元素)的物件屬性。雙眼要盯著可能越來越成長的變數快取

堆分析儀

拍一個快照

在Profiles面板中,選擇Take Heap Snapshot,然後點選Start或按Cmd + E或Ctrl + E:

快照最初是儲存在渲染器進程記憶體中的。它們被按需匯入到了DevTools中,當你點擊快照按鈕後就可以看到它們了。當快照被載入DevTools中顯示後,快照標題下面的數字顯示了能夠被引用到的(reachable)JavaScript物件佔有記憶體總數。

範例:試試看garbage collection in action的例子,在時間軸(Timeline)面板中監控記憶體的使用。

清除快照

點選Clear all按鈕圖示(),就能清除所有快照:

注意:關閉DevTools視窗並不能從渲染記憶體中刪除掉收集的快照。當重新開啟DevTools後,先前的快照清單還在。

記住我們之前提到的,當你產生快照時你可以強制執行在DevTools中GC。當我們拍快照時,GC是自動執行的。在時間軸(Timeline)中點選垃圾桶(垃圾回收)按鈕()就可以輕鬆的執行垃圾回收了。

範例:試試看scattered objects並用堆疊分析儀(Heap Profiler)分析它。你可以看到(對象)項目的集合。

切換快照檢視

一個快照可以根據不同的任務切換檢視。可以透過如圖的選擇框切換:

以下是三個預設視圖:

  • ##Summary(概要)  - 透過建構函數名分類顯示物件;

  • #Comparison(對照) - 顯示兩個快照間物件的差異;

  • Containment(控制) - 可用來探測堆內容;

Dominators(支配者)視圖可以在Settings面板中開啟– 顯示dominators tree. 可以用來找到記憶體增長點。

透過不同顏色區分物件

物件的屬性和屬性值有不同的類型並自動的透過顏麼進行了區分。每個屬性都是以下四種之一:

命名為

System的物件沒有對應的JavaScript類型。它們是JavaScript VM物件系統內建的。 V8將大多數內建物件和使用者JS物件放在同一個堆中。但它們只是V8的內部物件

視圖詳解

Summary view(概要視圖)

開啟一個快照,預設是以概要視圖顯示的,顯示了物件總數,可以展開顯示具體內容: Initially , a snapshot opens in the Summary view,

displaying object totals, which can be expanded to show instances:

第一層級是」總體」行,它們顯示了:

  • Constructor(建構子)表示所有透過此建構函式產生的物件

  • 物件的實例數在Objects Count列上顯示

  • #Shallow size列顯示了由對應建構子產生的物件的shallow sizes(直接佔用記憶體)總數

  • Retained size列展示了對應物件所佔用的最大記憶體

  • Distance列顯示的是物件到達GC根的最短距離

展開一個總體行後,會顯示所有的物件實例。沒一個實例的直接佔用記憶體和占用總記憶體都被對應顯示。 @符號後的數字不物件的唯一ID,有了它你就可以逐個物件的在不同快照間作對比。

範例:試試這個範例(在新tab標籤中開啟)來了解如何使用概要檢視。

記住黃色的物件被JavaScript引用,而紅色的物件是由黃色背景色引用被分離了的節點。

Comparison view(對照視圖)

此視圖用來對照不同的快照來找出快照之間的差異,來發現有記憶體洩漏的物件。來證明對應用的某個操作沒有造成洩漏(例如:一般一對操作和撤消的動作,像找開一個document,然後關閉,這樣是不會造成洩漏的),你可以按以下的步驟嘗試:

  1. 在操作前拍一個堆疊快照;

  2. 執行一個動作(做你認為會造成洩漏的動作);

  3. 執行一個動作(做你認為會造成洩漏的動作);

  4. #撤銷先前的操作(上一個操作相反的操作,多重複幾次);

#拍第二個快照,將視圖切換成對照視圖,並同快照1進行比較。

在對照視圖下,兩個快照之間的差異就會展現出來了。當展開一個總類別目後,增加和刪除了的物件就顯示出來了:

#範例:嘗試範例(在新tab標籤中開啟)來了解如何使用對照視圖來定位記憶體洩漏。

Containment view(控制視圖)

    控制視圖可以稱作對你的應用的物件結構的」鳥瞰視圖(bird’s eys view)」。它能讓你查看function內部,像你的JavaScript對像一樣的觀察VM內部對象,能讓你在你的應用的非常低層的記憶體使用情況。
  • 此檢視提供了幾個進入點:

  • DOMWindow 物件

     - 這些物件是JavaScript程式碼的」全域」物件;

  • GC根

     - VM的垃圾回收器真正的GC根;

  • ##Native物件
 - 瀏覽器物件對」推入」JavaScript虛擬機器中來進行自動操作,如:DOM節點,CSS規則(下一節會有詳細介紹。)

下圖是一個典型的控制視圖:

範例:試試範例(在新tab標籤中開啟)來了解如何使用控制視圖來檢視閉包

內部和

事件處理

關於閉包的建議

給函數命名對你在快照中的閉包函數間作出區分會很用幫助。如:下面的例子中沒有給函數命名:

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;
}

例子:試試這個例子why eval is evil來分析記憶體中閉包的影響。你可能也會對嘗試下面這個例子,記錄heap allocations(堆分配)有興趣。

揭露DOM記憶體洩漏

這個工具獨一無二的一點是展示了瀏覽器原生物件(DOM節點,CSS規則)和JavaScript物件之間的雙向參考。這能幫助你發現因為忘記解除引用遊離的DOM子節點而導致的難以發覺的記憶體洩漏。

DOM記憶體洩漏可能會超出你的想像。看下下面的例子 – #tree物件什麼時候被GC呢?

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 can be #tree GC
#leaf代表了對它的父節點的引用(parentNode)它遞歸引用到了#tree

,所以,只有當leafRef被nullified後

#tree代表的整個樹結構才會被GC回收。

#########

範例:嘗試leaking DOM nodes來了解哪裡DOM節點會記憶體洩漏並如何定位。你也可以看這個範例:DOM leaks being bigger than expected。

查看Gonzalo Ruiz de Villa的文章Finding and debugging memory leaks with the Chrome DevTools來閱讀更多關於DOM內存洩漏和內存分析的基礎。

原生物件在Summary和Containment視呼中更容易找到– 有它們專門的類別目:

範例:嘗試下這個範例(在新tab標籤中開啟)來了解如何將DOM樹分離。

支配者視圖(Dominators view)

支配者視圖顯示了堆圖的支配者樹。支配者視圖跟控制(Containment)視圖很像,但沒有屬性名稱。這是因為支配者可能會是沒有直接引用的對象,就是說這個支配者樹不是堆圖的生成樹。但這是個有用的視圖能幫助我們很快的定位記憶體成長點。

注意:在Chrome Canary中,支配者視圖能夠在DevTools中的Settings > Show advanced heap snapshot properties 開啟,重啟DevTools生效。

範例:試試這個範例(在新tab標籤中開啟)來練習如何找到記憶體增長點。可以進一步嘗試下一個範例retaining paths and dominators。

物件分配追蹤器

物件追蹤器整合了heap profiler的快照增量更新分析和Timeline面板的記錄。跟其它工具一樣,記錄物件的堆疊配置需要啟動記錄,執行一系列操作,然後停止記錄然後進行分析。

物件追蹤器不間斷的記錄堆快照(頻率達到了每50毫秒!),結束時記錄最後一個快照。此堆分配分析器顯示物件在哪被建立並定位它的保留路徑。

開啟並使用物件分析器

#開始使用物件分析器: 1. 確認你使用的是最新版的Chrome Canary。

  1. 打開DeveTools並點擊齒輪圖示(譯者:沒明白這步有什麼用)。

  2. 現在,打開Profiler面板,你就能看到」Record Heap Allocations」的選項。

上面的柱條表示在堆中產生的新物件。高度就對應了對應物件的大小,它的顏色表示了這個物件是否在最後拍的那個快照中還在:藍色柱表示在timeline最後這個物件還在,灰色柱表示這個物件在timeline中生成,但結束前已經被記憶體回收了。

在上面的範例中,一個動作執行了10次。同一個程式保留了5個對象,所以最後5個藍色柱條被保留了。但這最後留下的柱存在潛在的問題。你可以用timeline上的滑動條縮小到那個特定的快照並找到這個分配的物件。

點擊堆疊中的物件就能在堆快照的下面部分顯示它的保留總記憶體樹。檢查這個物件的保留總記憶體樹能夠給你足夠的資訊來了解為什麼這個物件沒有被回收,然後你就能對程式碼做相應的修改來去掉不必要的引用。

記憶體分析FAQ

問:我看不到物件的所有屬性,我也看到它們的非字串值!為什麼?

不是所有屬性都完整的保存在JavaScript堆中。其中有些是透過執行原生程式碼的getters方法來取得的。這些屬性沒有在堆快照中捕獲,是為了防止對getters方法的調用和避免程序狀態的改變,如果這些getters方法不是”純(Pure)”的functions。同樣,非字串的值,如數字,沒有被捕獲是為了減少快照的大小。

問:@符號後面的數字是什麼意思 – 是位址還是ID呢?這個ID值真的是唯一的麼?

這是物件ID。顯示物件的位址沒有意義,因為一個物件會在垃圾回收的時候被移除。這些物件IDs是真正的IDs – 就是說,它們在不同的快照間是唯一表示的。這樣就可以的堆狀態間進行精確的比較。維持這些IDs會為GC流程增加額外的開支,但這僅在記錄第一次堆快照時分配 – 如果堆分析儀沒有用到,就不會有額外的開支。

問:「死」(無法引用到的)物件被包含在快照中了麼?

沒有,只有可以引用到的物件才會顯示在快照中。而且,拍快照前都會先自動執行GC操作。

注意:在寫這篇文章的時候,我們計畫在拍快照的時候不再GC,防止堆疊尺寸的減少。現在已經是這樣了,但垃圾物件依然顯示在快照之外。

問:GC根是由什麼組成的?

由許多部會分組成:

  • #原生物件圖;

  • 符號表;

  • VM執行緒中的堆疊;

  • #編輯快取;

  • 控制器上下文;

  • 全域控制器。

問:我得知可以使用Heap Profiler和Timeline Memory view來偵測記憶體洩漏。但我該先用哪個工具呢?

Timeline面版,是在你第一次使用你的頁面發現速度變慢了時用來論斷過多的記憶體使用。網站變慢是比較典型的記憶體洩漏的訊號,但也可能是其它的原因 – 可能是有渲染或網路傳輸方面的瓶頸,所以要確保解決你網頁的真正問題。

論斷是否是記憶體問題,就開啟Timeline面板和Memory標籤。點擊record按鈕,然後在你的應用程式上重複幾次你認為可能導致記憶體洩漏的操作。停止記錄。你應用的記憶體使用圖就生成出來了。如果記憶體的使用一直在增長(而沒有相應的下降),這就表示你的應用可能有記憶體洩漏了。

一般一個正常的應用的記憶體使用圖形是鋸齒狀的,因為記憶體使用後又會被垃圾回收器回收。不用擔心這種鋸齒形 – 因為總是會因為JavaScript而有記憶體的消耗,甚至一個空的requestAnimationFrame也會造成這種鋸齒形,這是無法避免的。只要不是那種分配了持續很多記憶體的形狀,那就表示產生了很多記憶體垃圾。

上圖的成長線是需要你警覺的。在診斷分析的時候Memory標籤中的DOM node counter,Document counter和Event listener count也是很有用的。 DOM節點數是使用的原生記憶體不會影響JavaScript記憶體圖。

一旦你確認你的應用程式有記憶體洩漏,堆分析儀就可以用來找到記憶體洩漏的地方。

問:我發現堆快照中有的DOM節點的數字是用紅色標記為”Detached DOM tree”,而其它的是黃色的,這是什麼意思呢?

你會發現有不同的顏色。紅色的節點(有著深色的背景)沒有從JavaScript到它們的直接的引用,但它們是分離出來的DOM結構的一部分,所以他們還是在內存中保留了。有可能有一個節點被JavaScript引用到了(可能是在閉包中或一個變數),這個引用會阻止整個DOM樹被記憶體回收。

黃色節點(黃色背景)有JavaScript的直接引用。在同一個分離的DOM樹中查看一個黃色的節點來定位你的JavaScript的引用。就可能看到從DOM window到那個節點的屬性引用鏈(如:window.foo.bar[2].baz)。

下面的動態圖顯示了分離節點的處理過程:

#範例:試試這個範例detached nodes你可以檢視節點在Timeline中的生命週期,然後拍堆快照來找到分離的節點。

問:直接佔用記憶體(Shallow Size)和占用總記憶體(Retained Size)分別代表什麼,它們的差別是什麼?

是這樣的,物件可以在記憶體中以兩種方式存在(be alive) – 直接的被別一個可訪問的(alive)物件保留(window和document物件總是可訪問的)或被原生物件(象DOM物件)隱含的包留引用。後一種方式會因為阻止物件被GC自動回收,而有導引記憶體洩泥漏的可能。物件本身佔用的記憶體稱為直接佔用記憶體(通常來說,陣列和字串會保留更多的直接佔用記憶體(shallow size))。

一個任意大小的物件可以透過阻止其它物件記憶體被回收在保留很大的記憶體使用中。當一個物件被刪除後(它造成的一些依賴就無法被引用了)能夠釋放的記憶體的大小被稱為佔用總記憶體(retained size)。

問:constructor和retained欄位下有很多的資料。我該從哪裡開始調查我是的否遇到了記憶體洩漏呢?

一般來說最好是從透過retainers排序的第一個物件開始,retainers之間是透過距離排序的(是指到window物件的距離)。

距離最短的物件有可能是首選的可能導致記憶體洩漏的物件。

問:Summary, Comparison, Dominators 和 Containment這些視圖之間的差異是什麼?

你可以透過切換視圖來體驗它們的差異。

  • Summary(概要)視圖能幫你透過建構函式分組尋找物件(和物件的記憶體使用)。該視圖對找出DOM記憶體洩漏很有幫助。

  • Comparison(對照)視圖能夠透過顯示哪些物件記憶體被正確的回收了來搜尋記憶體洩漏。通常在一個操作前後記錄兩個(或更多)的記憶體使用快照。它是透過察看釋放的記憶體和引用數目的差導來察看是否有記憶體洩漏,並找到原因。

  • Containment(控制)視圖對物件結構有更好的展示,幫助我們分析全域作用域(如 window)中物件參考情況來找到是什麼保留了這些物件。它能讓你分析閉包並深入到物件更深層去查看。

  • Dominators(支配者)視圖能用來幫助我們確認沒有多餘的物件還掛在某個位置(如那些被引用了的),和確認物件的刪除/垃圾回收真正起了作用。

問:堆分析儀中的constructor(一組)內容代表什麼?

  • (global property) - 全域物件(像'window')和引用它的物件之間的中間對象。如果一個物件由建構函式Person產生並被全域物件引用,那麼引用路徑就是這樣的:[global] > (global property) > Person。這跟一般的直接引用彼此的物件不一樣。我們用中間物件是有效能方面的原因,全域物件改變會很頻繁,非全域變數的屬性存取最佳化對全域變數來說並不適用。

  • (roots) - constructor中roots的內容引用它所選取的物件。它們也可以是由引擎自主創建的一些引用。這個引擎有用於引用物件的緩存,但是這些引用不會阻止引用物件被回收,所以它們不是真正的強引用(FIXME)。

  • (closure) - 一些函數閉包中的一組物件的引用

  • (array, string, number, regexp) - 一組屬性引用了Array,String,Number或正規表示式的物件類型

  • #(compiled code) - 簡單來說,所有東西都與compoled code有關。 Script像一個函數,但其實對應了3f1c4e4b6b16bbbd69b2ee476dc4f83a的內容。 SharedFunctionInfos (SFI)是函數和compiled code之間的物件。函數通常有內容,而SFIS沒有(FIXME)。

  • HTMLpElementHTMLAnchorElementDocumentFragment 等– 你程式碼中對elements或document物件的引用。

在你的程式的生命週期中產生的許多其它的對象,包括事件監聽器或自訂對象,可以在下面的controllers中找到:

問:我在做記憶體分析時需要關閉Chrome裡可能會產生影響的什麼功能麼?

我們建議在用Chrome DevTools做記憶體分析時,你可以使用關閉所有擴充功能的隱身模式,或設定使用者資料夾為(--user-data-dir=" ")後再打開Chrome。

應用,擴展甚至console中的記錄都會對你的分析有潛在的影響,如果你想讓你的分析可靠的話,禁用這些吧。

寫在最後的話

今天的JavaScript引擎已經具有很強的能力,能夠自動回收程式碼產生的記憶體垃圾。就是說,它們只能做到這樣了,但我們的應用仍然被證明會因為邏輯錯誤而產生記憶體洩漏。使用相應的工具來找到應用的瓶頸,記住,不要靠猜 – 測試它。

幫助實例

診斷記憶體洩漏

儘管很多內容在本文章中已經提到了,但一系列測試記憶體相關的問題的例子還是很有用的,下面是一組DOM節點記憶體洩漏的例子。你可能希望在測試你的更複雜的頁面或應用之前先用這些例子做試驗。

  • Example 1: Growing memory

  • Example 2: Garbage collection in action

  • Example 3 : Scattered objects

  • Example 4: Detached nodes

  • Example 5: Memory and hidden classes

  • Example 6: Leaking DOM nodes

  • #Example 7: Eval is evil (almost always)

  • #Example 8 : Recording heap allocations

  • Example 9: DOM leaks bigger than expected

  • Example 10: Retaining path

  • ##Example 10: Retaining path

### ###Example 11: Last exercise################

以上是Chrome開發者工具之JavaScript記憶體分析的圖文介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn