Chrome開發者工具詳解(4)-Profiles面板
如果上篇中的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函數角度記錄內存的分配信息。
Record JavaScript CPU Profile簡介
通過選擇Record JavaScript CPU Profile,然后點擊Start,結合你所要分析的具體場景,你可以重新加載網頁,或者在網頁上進行交互,甚至什么都不操作。最后點擊Stop,完成記錄操作。
有三種不同的視圖可供選擇:
- Chart 按時間先后順序顯示的火焰圖。
- Heavy(Bottom Up) (自底向上)根據對性能的消耗影響列出所有的函數,并可以查看該函數的調用路徑。
- Tree(Top Down) (自頂向下) 從調用棧的頂端(最初調用的位置)開始,顯示調用結構的總體的樹狀圖情況。
我們以Chart視圖為例分析一下JS的執行的性能情況:
該視圖會以時間順序展示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對象總的大小。
堆快照提供了不同的視角來進行查看:
- Summary 該視圖按照構造函數進行分組,用它可以捕獲對象和它們使用的內存情況,對于跟蹤定位DOM節點的內存泄漏特別有用。
- Comparison 對比兩個快照的差別,用它可以對比某個操作前后的內存快照。分析操作前后的內存釋放情況以及它的引用計數,便于你確認內存是否存在泄漏以及造成的原因。
- Containment 該視圖可以探測堆的具體內容,它提供了一個更適合的視圖來查看對象結構,有助于分析對象的引用情況,使用它可以分析閉包和進行更深層次的對象分析。
- Statistics 統計視圖。
Summary視圖
該視圖會顯示所有的對象信息,點擊其中的一個對象進行展開可查看更詳細的實例信息。鼠標移動到某個對象上會顯示該對象實例的詳情信息。
圖中的各列的具體含義如下:
- Constructor 顯示所有的構造函數,點擊每一個構造函數可以查看由該構造函數創建的所有對象。
- Distance 顯示通過最短的節點路徑到根節點的距離。
- Objects Count 顯示對象的個數和百分比。
- Shallow size 顯示由特定的構造函數創建的所有對象的本身的內存總數。
- Retained size 顯示由該對象及其它所引用的對象的總的內存總數。
Shallow size和Retained size的區別?Shallow size是對象本身占用內存的大小,不包含它所引用的對象。Retained size是該對象本身的Shallow size,加上能從該對象直接或者間接訪問到對象的Shallow size之和。也就是說Retained size是該對象被GC之后所能回收到內存的總和。
在展開構造函數,則會列出該函數相關的所有對象實例,可以查看該對象的Shallow size和Retained size,在@符號后面的數字是該對象的唯一標識ID。
其中黃色的對象表示在它被某個JS所引用,而紅色的對象表示由黃色背景色引用被分離開出的節點。
這些構造函數都代表什么含義呢?
- (global property) 全局對象(比如
window
)和通過它引用的對象之間的中間對象,如果一個對象是由Person構造函數生成并被全局對象所引用,那么它們的引用路徑關系就像這樣[global] > (global property) > Person。這跟常規的對象之間直接引用相比,采用中間對象主要是考慮性能的原因。全局對象的改變是很頻繁的,而非全局變量的屬性訪問最優化方案對全局變量是不適用的。 - (roots) 它們可以是由引擎自己的目標創建的一些引用,這個引擎可以緩存引用的對象,但所有的這些引用都是弱引用,它們不會阻止引用對象被回收。
- (closure) 一些函數閉包中的一組對象的引用。
- (array, string, number, regexp) 一系屬性引用了數組(Array),字符串(String),數字(Number)或正則表達式的對象類型。
- HTMLDivElement, HTMLAnchorElement, DocumentFragment等 你的代碼中對元素(elements)的引用或者指定的document對象的引用。
Comparison視圖
通過比較多個快照之間的差異來找出內存泄露的對象,為了驗證某個程序的操作不會引起內存泄露(通常會執行一個操作后再執行一個對應的相反操作,比如打開一個文檔后再關閉它,應該是沒有產生內存泄露問題的),你可以執行如下步驟:
- 在執行一個操作之前拍一個快照。
- 執行一個操作,通過你認為可能會引起內存泄露的一次頁面交互操作。
- 執行一個相反的操作。
- 拍第二個快照,切換到Comparison視圖,并與第一個快照進行對比。
切換到Comparison視圖之后,就可以看到兩個不同的快照之間的差別。
Containment視圖
該視圖本質上就是應用程序的對象結構的“鳥瞰圖”,允許你去深入分析函數的閉包,了解應用程序底層的內存使用情況。
這個視圖提供了多個入口:
- DOMWindow objects DOMWindow對象,即JS代碼全局對象。
- Native objects 瀏覽器原生對象,比如DOM節點,CSS規則。
閉包小建議: 在快照的分析中命名函數的閉包相比匿名函數的閉包更容易區分。
Google上提供的例子和圖如下:
function createLargeClosure() {var largeStr = new Array(1000000).join('x');var lC = function() { // 匿名函數return largeStr;};return lC;
}
function createLargeClosure() {var largeStr = new Array(1000000).join('x');var lC = function lC() { // 命名函數return largeStr;};return lC;
}
Statistics視圖
該視圖是堆快照的總的分布統計情況,這個直接上圖就可以了:
內存泄露示例
還是把Google提供的內存泄露的小例子貼出來:
DOM內存泄露可能比你想象的要大,考慮一下下面的例子-什么時候#tree節點被釋放掉?
var select = document.querySelector;var treeRef = select("#tree");var leafRef = select("#leaf");var body = select("body");body.removeChild(treeRef);//由于treeRef #tree不能被釋放treeRef = null;//由于leafRef的間接引用 #tree還是不能被釋放leafRef = null;//現在沒有被引用,#tree這個時候才可以被釋放了
#leaf
節點保持著對它的父節點(parentNode)的引用,這樣一直遞歸引用了#tree
節點,所以只有當leafRef
被設置成null后,#tree
下面的整個樹節點才有可能被垃圾回收器回收。
Record Allocation Timeline簡介
該工具是可以幫助你追蹤JS堆里面的內存泄漏的另一大利器。
選中Record Allocation Timeline按鈕,點擊Start按鈕之后,執行你認為可能會引起內存泄漏的操作,操作之后點擊左上角的停止按鈕即可。你可以在藍色豎線上通過縮放來過濾構造器窗格來僅僅顯示在指定的時間幀內的被分配的對象。
錄制過程中,在時間線上會出現一些藍色豎條,這些藍色豎條代表一個新的內存分配,這個新的內存分配都可以會有潛在的內存泄露問題。
通過展開對象并點擊它的值則可以在Object窗格中查看更多新分配的對象細節。
Record Allocation Profile簡介
從JS函數角度記錄并查看內存的分配信息。點擊Start按鈕,執行你想要去深入分析的頁面操作,當你完成你的操作后點擊Stop按鈕。然后會顯示一個按JS函數進行內存分配的分解圖,默認的視圖是Heavy (Bottom Up),該視圖會把最消耗內存的函數顯示在最頂端。
下圖是切換到Chart視圖時具體的界面,點擊任意函數跳轉到Sources面板可以查看具體的函數信息。
參考文檔
- Uncovering DOM Leaks
- Shallow and retained sizes
個人博客
我的個人博客