Chrome開發者工具詳解(4):Profiles皮膚

CharlieChu發表於2016-11-27

如果上篇中的Timeline皮膚所提供的資訊不能滿足你的要求,你可以使用Profiles皮膚,利用這個皮膚你可以追蹤網頁程式的記憶體洩漏問題,進一步提升程式的JavaScript執行效能

概述

當前使用的Chrome最新版為54.0.2840.71,這個版本的Profiles皮膚比之前提供的功能更多也更強大,下面是該皮膚所包含的功能點:

  • Record JavaScript CPU Profile 用於分析網頁上的JavaScript函式在執行過程中的CPU消耗資訊。
  • Take Heap Snapshot 建立堆快照用來顯示網頁上的JS物件和相關的DOM節點的記憶體分佈情況。
  • Record Allocation Timeline 從整個Heap角度記錄記憶體的分配資訊的時間軸資訊,利用這個可以實現隔離記憶體洩漏問題。
  • Record Allocation Profile 從JS函式角度記錄記憶體的分配資訊。

profile-panel

Record JavaScript CPU Profile簡介

通過選擇Record JavaScript CPU Profile,然後點選Start,結合你所要分析的具體場景,你可以重新載入網頁,或者在網頁上進行互動,甚至什麼都不操作。最後點選Stop,完成記錄操作。

有三種不同的檢視可供選擇:

  • Chart 按時間先後順序顯示的火焰圖。

profile-1-chart

  • Heavy(Bottom Up) (自底向上)根據對效能的消耗影響列出所有的函式,並可以檢視該函式的呼叫路徑。

profile-1-heavy

  • Tree(Top Down) (自頂向下) 從呼叫棧的頂端(最初呼叫的位置)開始,顯示呼叫結構的總體的樹狀圖情況。

profile-1-tree

我們以Chart檢視為例分析一下JS的執行的效能情況:

profile-js-cpu-chart

該檢視會以時間順序展示CPU的效能情況,檢視主要分成兩塊:

  • Overview 整個錄製結果的鳥瞰圖(概覽),柱形條的高度對應了呼叫堆疊的深度,也就是說柱形條高度越高,呼叫堆疊的深度越深。
  • Call Stacks 在錄製過程中被呼叫的函式的深入分析檢視(呼叫堆疊),橫軸表示時間,縱軸表示呼叫棧,自上而下的表示函式的呼叫情況。也就是說上面的函式呼叫在它下面的函式。

檢視中的函式顏色不同於其它的皮膚,這裡面的函式顏色標記是隨機顯示的。然而相同的函式呼叫顏色標記是相同的。

其中縱軸表示的函式呼叫堆疊高度僅僅函式的呼叫巢狀層次比較深,不表示其重要性很高,但是橫軸上一個很寬的柱形條則意味著函式的呼叫需要一個很長的時間去完成,那麼你就考慮去做一些優化操作,具體可以參見網路效能優化方案及裡面的相關參考文件。

將滑鼠移到Call Stacks中的函式上可以顯示函式的名稱和時間相關的資料,會提供如下資訊:

  • Name 函式名稱
  • Self time 函式的本次呼叫執行的時間,僅僅包含該函式本身的執行時間,不包含它所呼叫的子函式的時間。
  • Total time 函式的本次呼叫執行的總時間,包含它所呼叫的子函式的執行時間。
  • URL 函式定義在檔案中所在的位置,其格式為file.js:100,表示函式在file.js檔案中的第100行。
  • Aggregated self time 在這次的錄製過程中函式呼叫執行的總時間,不包含它所呼叫的子函式的時間。
  • Aggregated total time 在這次的錄製過程中所有的函式呼叫執行的總時間,包含它所呼叫的子函式的時間。
  • Not optimized 如果優化器檢測到該函式有潛在的優化空間,那麼該函式會被列在這裡。

Take Heap Snapshot簡介

通過建立堆快照可以檢視建立快照時網頁上的JS物件和DOM節點的記憶體分佈情況。利用該工具你可以建立JS的堆快照、記憶體分析圖、對比堆快照以及定位記憶體洩漏問題。選中Take Heap Snapshot,點選Take Snapshot按鈕即可獲取快照,在每一次獲取快照前都會自動執行垃圾回收操作。

快照最初會儲存在渲染程式的記憶體之中,當我們點選建立快照按鈕來檢視時才會被傳輸到DevTools中,當快照被載入到DevTools裡面並經過解析之後,在快照標題下方的文字顯示是數字就是可訪問到的JS物件總的大小。

profile-2-overview

堆快照提供了不同的視角來進行檢視:

  • Summary 該檢視按照建構函式進行分組,用它可以捕獲物件和它們使用的記憶體情況,對於跟蹤定位DOM節點的記憶體洩漏特別有用。
  • Comparison 對比兩個快照的差別,用它可以對比某個操作前後的記憶體快照。分析操作前後的記憶體釋放情況以及它的引用計數,便於你確認記憶體是否存在洩漏以及造成的原因。
  • Containment 該檢視可以探測堆的具體內容,它提供了一個更適合的檢視來檢視物件結構,有助於分析物件的引用情況,使用它可以分析閉包和進行更深層次的物件分析。
  • Statistics 統計檢視。

Summary檢視

該檢視會顯示所有的物件資訊,點選其中的一個物件進行展開可檢視更詳細的例項資訊。滑鼠移動到某個物件上會顯示該物件例項的詳情資訊。

profile-2-summary

圖中的各列的具體含義如下:

  • Constructor 顯示所有的建構函式,點選每一個建構函式可以檢視由該建構函式建立的所有物件。
  • Distance 顯示通過最短的節點路徑到根節點的距離。
  • Objects Count 顯示物件的個數和百分比。
  • Shallow size 顯示由特定的建構函式建立的所有物件的本身的記憶體總數。
  • Retained size 顯示由該物件及其它所引用的物件的總的記憶體總數。

Shallow sizeRetained size的區別?Shallow size是物件本身佔用記憶體的大小,不包含它所引用的物件。Retained size是該物件本身的Shallow size,加上能從該物件直接或者間接訪問到物件的Shallow size之和。也就是說Retained size是該物件被GC之後所能回收到記憶體的總和。

在展開建構函式,則會列出該函式相關的所有物件例項,可以檢視該物件的Shallow size和Retained size,在@符號後面的數字是該物件的唯一標識ID。

其中黃色的物件表示在它被某個JS所引用,而紅色的物件表示由黃色背景色引用被分離開出的節點。

profile-2-summary2

這些建構函式都代表什麼含義呢?

  • (global property) 全域性物件(比如window)和通過它引用的物件之間的中間物件,如果一個物件是由Person建構函式生成並被全域性物件所引用,那麼它們的引用路徑關係就像這樣[global] > (global property) > Person。這跟常規的物件之間直接引用相比,採用中間物件主要是考慮效能的原因。全域性物件的改變是很頻繁的,而非全域性變數的屬性訪問最優化方案對全域性變數是不適用的。
  • (roots) 它們可以是由引擎自己的目標建立的一些引用,這個引擎可以快取引用的物件,但所有的這些引用都是弱引用,它們不會阻止引用物件被回收。
  • (closure) 一些函式閉包中的一組物件的引用。
  • (array, string, number, regexp) 一系屬性引用了陣列(Array),字串(String),數字(Number)或正規表示式的物件型別。
  • HTMLDivElement, HTMLAnchorElement, DocumentFragment等 你的程式碼中對元素(elements)的引用或者指定的document物件的引用。

Comparison檢視

通過比較多個快照之間的差異來找出記憶體洩露的物件,為了驗證某個程式的操作不會引起記憶體洩露(通常會執行一個操作後再執行一個對應的相反操作,比如開啟一個文件後再關閉它,應該是沒有產生記憶體洩露問題的),你可以執行如下步驟:

  1. 在執行一個操作之前拍一個快照。
  2. 執行一個操作,通過你認為可能會引起記憶體洩露的一次頁面互動操作。
  3. 執行一個相反的操作。
  4. 拍第二個快照,切換到Comparison檢視,並與第一個快照進行對比。

profile-2-comparison

切換到Comparison檢視之後,就可以看到兩個不同的快照之間的差別。

Containment檢視

該檢視本質上就是應用程式的物件結構的“鳥瞰圖”,允許你去深入分析函式的閉包,瞭解應用程式底層的記憶體使用情況。

這個檢視提供了多個入口:

  • DOMWindow objects DOMWindow物件,即JS程式碼全域性物件。
  • Native objects 瀏覽器原生物件,比如DOM節點,CSS規則。

profile-2-containment

閉包小建議: 在快照的分析中命名函式的閉包相比匿名函式的閉包更容易區分。

Google上提供的例子和圖如下:

domleaks

Statistics檢視

該檢視是堆快照的總的分佈統計情況,這個直接上圖就可以了:

profile-2-statistics

記憶體洩露示例

還是把Google提供的記憶體洩露的小例子貼出來:

DOM記憶體洩露可能比你想象的要大,考慮一下下面的例子-什麼時候#tree節點被釋放掉?

profile-treegc

#leaf節點保持著對它的父節點(parentNode)的引用,這樣一直遞迴引用了#tree節點,所以只有當leafRef被設定成null後,#tree下面的整個樹節點才有可能被垃圾回收器回收。

Record Allocation Timeline簡介

該工具是可以幫助你追蹤JS堆裡面的記憶體洩漏的另一大利器。

選中Record Allocation Timeline按鈕,點選Start按鈕之後,執行你認為可能會引起記憶體洩漏的操作,操作之後點選左上角的停止按鈕即可。你可以在藍色豎線上通過縮放來過濾構造器窗格來僅僅顯示在指定的時間幀內的被分配的物件。

錄製過程中,在時間線上會出現一些藍色豎條,這些藍色豎條代表一個新的記憶體分配,這個新的記憶體分配都可以會有潛在的記憶體洩露問題。

profile-3-summary

通過展開物件並點選它的值則可以在Object窗格中檢視更多新分配的物件細節。

profile-3-summary-object

Record Allocation Profile簡介

從JS函式角度記錄並檢視記憶體的分配資訊。點選Start按鈕,執行你想要去深入分析的頁面操作,當你完成你的操作後點選Stop按鈕。然後會顯示一個按JS函式進行記憶體分配的分解圖,預設的檢視是Heavy (Bottom Up),該檢視會把最消耗記憶體的函式顯示在最頂端。

下圖是切換到Chart檢視時具體的介面,點選任意函式跳轉到Sources皮膚可以檢視具體的函式資訊。

profile-4-chart

參考文件

相關文章