修復內存問題

  了解如何使用Chrome DevTools查找影響網頁性能的內存問題,包括內存泄漏,內存膨脹和頻繁的垃圾回收。

  TL;DR

  使用Chrome任務管理器,了解您的網頁使用的內存量。

  使用時間軸記錄可視化內存使用。

  使用堆快照標識分離的DOM樹(內存泄漏的常見原因)。

  通過分配時間軸記錄了解在JS堆中分配新內存的時間。

  概述

  在[RAIL][RAIL]性能模型中,你的重點應該是你的用戶上。

  內存問題很重要,因為它們經常被用戶感知。用戶可以通過以下方式感知內存問題:

  • 網頁的性能效果會隨著時間的推移逐漸變差。這可能是內存泄漏的癥狀。內存泄漏是指頁面中的錯誤導致頁面逐漸使用越來越多的內存。

  • 網頁的效果始終不佳。這可能是內存膨脹的癥狀。內存膨脹是指當頁面使用的內存比最佳頁面速度所需的內存多出很多。

  • 網頁的效果延遲或頻繁暫停。這可能是頻繁的垃圾回收的癥狀。垃圾回收是指瀏覽器回收內存。瀏覽器決定何時回收內存。在回收內存期間,所有腳本執行都將暫停。所以如果瀏覽器頻繁的垃圾回收,,那腳本被暫停的次數也會很頻繁。

  內存膨脹︰ 多少才是“過多”?

  內存泄漏很容易定義。如果一個網站逐漸使用越來越多的內存,那么說明你的網頁有內存泄漏。但內存膨脹是有點難以定義。我們用什么來定義“使用太多的內存”呢?

  這個沒有硬性的準則,因為不同的設備和瀏覽器具有不同的性能表現。在高端智能手機上平滑運行的同一頁面在低端智能手機上可能崩潰。

  這里的關鍵是使用RAIL模型,并專注于您的用戶。了解你用戶主要使用哪些設備,然后在這些設備上測試您的網頁。如果表現一直不好,該頁面所需內存可能超出那些設備的內存存儲能力。

  使用Chrome任務管理器實時監控內存使用情況

  使用Chrome任務管理器作為檢測內存問題的起點。任務管理器是一個實時監視器,它告訴你一個頁面當前正在使用多少內存。

  • 按快捷鍵Shift + Esc 或 打開Chrome main menu(主選單),然后選擇More tools(更多工具) > Task manager(任務管理器),可以開啟任務管理器。

打開任務管理器

  • 右鍵點擊Task manager(任務管理器)表頭欄,并且啟用JavaScript memory(JavaScript 使用的內存)。

啟用JavaScript內存

  • 這兩列是在用不同角度來告訴你,網頁的內存使用情況︰

  Memory(內存)列 表示本機內存。DOM節點存儲在本機內存中。如果這個值在增加,則說明正在創建DOM節點。

  JavaScript Memory(JavaScript 使用的內存)列 表示JS堆。這一列包含兩個值。 您感興趣的值是會跳動的數字(括號中的數字)。跳動的數字表示您網頁上的可獲得的對象正在使用多少內存。如果這個數字在增加,那說明正在創建新對象,或現有對象正在增長。

  什么是可獲得的對象?

  瀏覽器的垃圾回收機制采用標記-清除垃圾回收算法。這個算法假定設置一個叫做根(root)的對象(在Javascript里,根是全局對象)。垃圾回收器將定期的從根開始,找所有從根開始引用的對象,然后找這些對象引用的對象……有引用的對象可獲得的對象(reachable objects),也叫活動對象(Live Object)。沒有引用的對象是不可獲得的對象(non-reachable objects),被認為是垃圾,可以被回收。

  從根開始,垃圾回收器將找到所有可以獲得的對象和所有不可獲得的對象。

  使用Timeline(時間軸)錄制可視化內存泄漏

  您還可以使用Timeline(時間軸)面板來查看內存使用情況。Timeline(時間軸)面板可為你更直觀地實時顯示頁面的內存使用。

  • 打開DevTools上的Timeline(時間軸)面板。

  • 啟用Memory(內存)復選框。

  • 進行錄音。

  提示:使用強制垃圾回收開始和結束你的錄制是一個好習慣。在錄制時,點擊collect garbage(垃圾回收)按鈕(強制垃圾回收按鈕)可以強制回收垃圾。

  為了演示時間軸內存錄制,考慮下面的代碼:

JavaScript代碼
  1. var x = [];  
  2.   
  3. function grow() {  
  4.   for (var i = 0; i < 10000; i++) {  
  5.     document.body.appendChild(document.createElement('div'));  
  6.   }  
  7.   x.push(new Array(1000000).join('x'));  
  8. }  
  9.   
  10. document.getElementById('grow').addEventListener('click', grow);  

  每次點擊代碼中引用的按鈕(ID為grow)時,1萬個div節點被附加到文檔<body>中,并且一個100萬個x字符的字符串被push到x數組中。運行此代碼將生成一個Timeline(時間軸)錄制,如以下截圖所示:

簡單增長的例子

  首先,介紹一下界面。Overview(概述)窗格(NET下面)中的HEAP(堆)曲線圖表示JS堆。 在Overview(概述)下方是Counter(計數器)窗格。在這里你可以看到內存使用情況(與Overview(概述)窗格中的HEAP(堆)曲線圖相同),分別顯示以下內容:JS heap(JS堆),documents(文檔),DOM nodes(DOM節點),listeners(偵聽器)和GPU memory(GPU內存)。勾選或取消勾選復選框可以將其從圖表中顯示或隱藏。

  現在,分析代碼與截圖進行比較。你可以看到節點計數器(綠色曲線),你可以看到它與代碼完全匹配。節點計數很規律的逐步增加。您可以認為節點計數的每次增加都是對grow()的調用。JS堆曲線圖(藍色曲線)就不是那么直截了當了。最佳實踐是,第一次下降實際上是一個強制垃圾回收(通過點擊collect garbage(垃圾回收)按鈕實現的)。隨著記錄的進行,你可以看到JS堆大小峰值。這是很自然,而且符合預期的:在每次按鈕(ID為grow)點擊時,JavaScript代碼創建DOM節點,并在創建100萬個字符的字符串時需要做大量工作。最關鍵的一點是,JS 堆結束時比起始點 (這里“起始點”是指強制垃圾回收之后的點) 高。在真實情況下,如果你看到JS堆曲線或節點曲線逐漸增加,它可能意味著內存泄漏。

  使用堆快照發現分離的DOM樹內存泄漏

  當一個DOM節點沒有來自頁面的DOM樹或JavaScript代碼的引用,他們就會被當做垃圾回收。當一個節點從DOM樹中移除時,它被稱為detached(分離DOM樹的節點),但是一些JavaScript仍然引用它。分離的DOM節點是內存泄漏的常見原因。本節教你如何使用DevTools的堆分析器來識別分離的節點。

  這里有一個簡單的分離DOM節點的例子。

JavaScript代碼
  1. var detachedNodes;  
  2.   
  3. function create() {  
  4.   var ul = document.createElement('ul');  
  5.   for (var i = 0; i < 10; i++) {  
  6.     var li = document.createElement('li');  
  7.     ul.appendChild(li);  
  8.   }  
  9.   detachedTree = ul;  
  10. }  
  11.   
  12. document.getElementById('create').addEventListener('click', create);  

  單擊代碼中引用的按鈕(ID為create)將創建一個具有十個li子節點的ul節點。這些節點由 JavaScript 代碼引用,但不存在于DOM樹中,因此它們是分離節點。

  堆快照是識別分離節點的一種方法。顧名思義,堆快照,在該快照的時間點上,顯示內存是如何分布在頁面的JS對象和DOM節點之間。

  要想創建快照,打開DevTools,并轉到Profiles(分析)面板,勾選Take Heap Snapshot(采集堆快照)單選按鈕,然后點擊Take Snapshot(采集快照)按鈕。

采集堆快照

  快照可能需要一些時間來處理和加載。一旦完成,在面板的左側(名為HEAP SNAPSHOTS(堆快照))的標簽下,選中它。

  在Class filter(類別過濾器)文本框中輸入Detached可以搜索分離的DOM樹。

過濾分離的節點

  點擊三角圖標,可以展開分離樹,查看研究詳情。

研究分離樹

  黃色高亮的節點表示是通過JavaScript代碼直接引用它們。紅色高亮的節點沒有直接引用。它們依然存在與內存中,因為它們是黃節點樹的一部分。一般來說,你應該把重點放在黃色的節點上。修復您的代碼,以便黃色節點不活動的時間比它活動的時間長,并且你也可以去除作為黃色節點樹一部分的紅色節點。

  點擊一個黃色的節點進一步研究。在Objects(對象)窗格中,您可以查看有關引用它的代碼的更多信息。例如,在下面的屏幕截圖中,您可以看到detachedTree變量引用了該節點。為了解決這個詳細說明的內存泄漏,您將研究使用detachedTree的代碼,并確保,當不再需要它的時候,刪除它對節點的引用。

研究黃色節點

  使用分配時間線識別JS堆內存泄漏

  Allocation Timeline(分配時間軸)是幫助您跟蹤JS堆中的內存泄漏的另一個工具。

  要演示分配時間線,請考慮以下代碼:

JavaScript代碼
  1. var x = [];  
  2.   
  3. function grow() {  
  4.   x.push(new Array(1000000).join('x'));  
  5. }  
  6.   
  7. document.getElementById('grow').addEventListener('click', grow);  

  每次點擊代碼中引用的按鈕(ID為'grow')時,將向x數組中添加100萬長度的字符串。

  要想錄制分配時間線,打開DevTools,轉到Profiles(分析)面板, 選擇Record Allocation Timeline(錄制分配時間軸)單選按鈕, 點擊Start(開始)按鈕,執行您懷疑導致內存泄漏的操作, 然后,當你完成時,點擊stop recording(停止錄制)按鈕(停止錄制按鈕)。

  在您錄制時,請注意是否有藍色條顯示在Allocation Timeline(分配時間軸)上,就像下面的截圖。

新分配的內存

  那些藍色條表示新的內存分配。這些新的內存分配可能就是內存泄漏點。您可以縮放條形來篩選,以便在Constructor(構造函數)”窗格只顯示在指定時間范圍內分配的對象。

放大分配時間軸

  展開對象并單擊其值,可以在Object(對象)窗格中查看其詳細信息。 例如,在下面的屏幕截圖中,通過查看新分配的對象的詳細信息, 您將能夠看到它已分配給Window作用域中的x變量。

  在Object面板中,點擊想要查看的對象,展開以查看更多詳細信息。例如,下面的屏幕截圖,通過查看新分配對象的詳細信息,你將能夠看到x變量在Window范圍內。

對象的詳細信息

  按函數查看內存分配

  使用Record Allocation Profiler(錄制分配分析器)類型,可以按JavaScript函數查看內存分配。

錄制分配分析器

  1. 勾選Record Allocation Profiler(錄制分配分析器)單選按鈕。如果頁面上有一個worker(這里指Service workers),您可以使用Start(開始)按鈕旁邊的下拉菜單選擇它作為性能分析目標。

  2. 按Start(開始)按鈕。

  3. 在要查看的頁面上執行操作。

  4. 當你完成所有操作后,按Stop(停止)按鈕。

  DevTools按函數顯示了內存分配的明細。默認視圖是Heavy (Bottom Up)(內存分配從高到底排序),顯示在最頂部的是分配內存最多的函數。

分配分析

  跟蹤頻繁的垃圾回收

  如果您的網頁頻繁出現卡頓現象,那么你的網頁可能有垃圾回收的問題。

  您可以使用Chrome Task Manager(Chrome任務管理器)或時間軸內存記錄來發現頻繁的垃圾回收。 在Task Manager(任務管理器)中,Memory或JavaScript Memory值頻繁地上升和下降代表垃圾回收頻繁。 在時間軸錄制中,JS堆或節點計數圖頻繁地上升和下降表明垃圾回收頻繁。

  一旦你確定了問題,您可以使用Allocation Timeline(分配時間軸)錄制來查找分配內存的位置和導致分配的函數。

除非特別注明,雞啄米文章均為原創
轉載請標明本文地址:http://www.vkzldl.live/software/749.html
2017年7月31日
作者:雞啄米 分類:軟件開發 瀏覽: 評論:0