瞭解如何使用 Chrome 和 DevTools 查找影響頁面性能的內存問題,包括內存泄漏、內存膨脹和頻繁的垃圾回收。javascript
在 RAIL 性能模型的精髓中,您的性能工做的焦點應是用戶。java
內存問題相當重要,由於這些問題常常會被用戶察覺。 用戶可經過如下方式察覺內存問題:web
內存泄漏很容易肯定。若是網站使用的內存愈來愈多,則說明發生內存泄漏。 但內存膨脹比較難以界定。 什麼狀況纔算是「使用過多的內存」?chrome
這裏不存在硬性數字,由於不一樣的設備和瀏覽器具備不一樣的能力。 在高端智能手機上流暢運行的相同頁面在低端智能手機上則可能崩潰。數組
界定的關鍵是使用 RAIL 模型並以用戶爲中心。瞭解什麼設備在您的用戶中深受歡迎,而後在這些設備上測試您的頁面。若是體驗一直糟糕,則頁面可能超出這些設備的內存能力。瀏覽器
使用 Chrome 任務管理器做爲內存問題調查的起點。 任務管理器是一個實時監視器,能夠告訴您頁面當前正在使用的內存量。app
按 Shift+Esc 或者轉到 Chrome 主菜單並選擇 More tools > Task manager,打開任務管理器。dom
右鍵點擊任務管理器的表格標題並啓用 JavaScript memory。 ide
下面兩列能夠告訴您與頁面的內存使用有關的不一樣信息:chrome-devtools
您也可使用 Timeline 面板做爲調查的起點。 Timeline 面板能夠幫助您直觀瞭解頁面在一段時間內的內存使用狀況。
提示:一種比較好的作法是使用強制垃圾回收開始和結束記錄。 在記錄時點擊 Collect garbage 按鈕 () 能夠強制進行垃圾回收。
要顯示 Timeline 內存記錄,請考慮使用下面的代碼:
var x = []; function grow() { for (var i = 0; i < 10000; i++) { document.body.appendChild(document.createElement('div')); } x.push(new Array(1000000).join('x')); } document.getElementById('grow').addEventListener('click', grow);
每次按代碼中引用的按鈕時,將向文檔正文附加 1 萬個 div
節點,並將一個由 100 萬個 x
字符組成的字符串推送到 x
數組。運行此代碼會生成一個相似於如下屏幕截圖的 Timeline 記錄:
首先,咱們來講明一下用戶界面。Overview 窗格中的 HEAP 圖表(NET 下方)表示 JS 堆。概覽窗格下方是計數器窗格。從這裏,您能夠看到內存使用按 JS 堆 (與 Overview 窗格中的 HEAP 圖表相同)、文檔、DOM 節點、偵聽器和 GPU 內存細分。停用對應的複選框能夠將其在圖表中隱藏。
如今,咱們將根據屏幕截圖來分析代碼。若是查看節點計數器(綠色圖表),您會看到它與代碼徹底匹配。節點計數以離散步長方式增大。 您能夠假定節點計數的每次增大都是對 grow()
的一次調用。 JS 堆圖表(藍色圖表)的顯示並不直接。爲了符合最佳作法,第一次降低其實是一次強制垃圾回收(經過按 Collect garbage 按鈕實現)。隨着記錄的進行,您會看到 JS 堆大小高低交錯變化。這種現象是正常的而且在預料之中:每次點擊按鈕,JavaScript 代碼都會建立 DOM 節點,在建立由 100 萬個字符組成的字符串期間,代碼會完成大量工做。這裏的關鍵是,JS 堆在結束時會比開始時大(這裏「開始」是指強制垃圾回收後的時間點)。在實際使用過程當中,若是您看到這種 JS 堆大小或節點大小不斷增大的模式,則可能存在內存泄漏。
只有頁面的 DOM 樹或 JavaScript 代碼再也不引用 DOM 節點時,DOM 節點纔會被做爲垃圾進行回收。 若是某個節點已從 DOM 樹移除,但某些 JavaScript 仍然引用它,咱們稱此節點爲「已分離」。已分離的 DOM 節點是內存泄漏的常見緣由。此部分將教您如何使用 DevTools 的堆分析器肯定已分離的節點。
下面是一個已分離 DOM 節點的簡單示例。
var detachedNodes; function create() { var ul = document.createElement('ul'); for (var i = 0; i < 10; i++) { var li = document.createElement('li'); ul.appendChild(li); } detachedTree = ul; } document.getElementById('create').addEventListener('click', create);
點擊代碼中引用的按鈕將建立一個包含 10 個 li
子級的 ul
節點。 這些節點由代碼引用,但不存在於 DOM 樹中,所以它們已分離。
堆快照是肯定已分離節點的一種方式。顧名思義,堆快照能夠爲您顯示拍攝快照時內存在您頁面的 JS 對象和 DOM 節點間的分配。
要建立快照,請打開 DevTools 並轉到 Profiles 面板,選擇 Take Heap Snapshot 單選按鈕,而後按 Take Snapshot 按鈕。
快照可能須要一些時間處理和加載。完成後,請從左側面板(名稱爲 HEAP SNAPSHOTS)中選擇該快照。
在 Class filter 文本框中鍵入 Detached
,搜索已分離的 DOM 樹。
以黃色突出顯示的節點具備 JavaScript 代碼對它們的直接引用。 以紅色突出顯示的節點則沒有直接引用。只有屬於黃色節點的樹時,它們才處於活動狀態。 通常而言,您須要將注意力放在黃色節點上。 修復代碼,使黃色節點處於活動狀態的時間不長於須要的時間,您也須要消除屬於黃色節點樹的紅色節點。
點擊黃色節點對其進行進一步調查。在 Object 窗格中,您能夠看到與正在引用該節點的代碼相關的更多信息。 例如,在下面的屏幕截圖中,您能夠看到 detachedTree
變量正在引用該節點。要解決這一特定的內存泄漏,您須要研究使用 detachedTree
的代碼並確保在不須要時,此代碼能夠移除其對節點的引用。
分配時間線是您能夠用於跟蹤 JS 堆中內存泄漏的另外一種工具。
要顯示分配時間線,請考慮使用下面的代碼:
var x =[];
function grow(){
x.push(newArray(1000000).join('x'));
}
document.getElementById('grow').addEventListener('click', grow);
每次按代碼中引用的按鈕時,都會向 x
數組添加一個由 100 萬個字符組成的字符串。
要記錄分配時間線,請打開 DevTools,而後轉到 Profiles 面板,選擇 Record Allocation Timeline 單選按鈕,按 Start 按鈕,執行您懷疑致使內存泄漏的操做。完成後,按 stop recording 按鈕 ()。
記錄時,請注意分配時間線上是否顯示任何藍色豎線(以下面的屏幕截圖所示)。
這些藍色豎線表示新內存分配。新內存分配中可能存在內存泄漏。 您能夠在豎線上放大,將 Constructor 窗格篩選爲僅顯示在指定時間範圍內分配的對象。
展開對象並點擊它的值,能夠在 Object 窗格中查看其更多詳情。 例如,在下面的屏幕截圖中,經過查看新分配對象的詳細信息,您能夠看到它被分配到 Window
做用域中的 x
變量。
使用 Record Allocation Profiler 類型可按 JavaScript 函數查看內存分配。
DevTools 按函數顯示內存分配明細。默認視圖爲 Heavy (Bottom Up),將分配了最多內存的函數顯示在最上方。
若是感受頁面常常暫停,則可能存在垃圾回收問題。
您可使用 Chrome 任務管理器或者 Timeline 內存記錄發現頻繁的垃圾回收。 在任務管理器中,Memory 或 JavaScript Memory 值頻繁上升和降低表示存在頻繁的垃圾回收。在 Timeline 記錄中,JS 堆或節點計數圖表頻繁上升和降低指示存在頻繁的垃圾回收。
肯定問題後,您可使用分配時間線記錄找出內存正在分配到什麼地方,以及哪些函數致使分配。