1. 程式人生 > >記憶體洩露從入門到精通三部曲之排查方法篇

記憶體洩露從入門到精通三部曲之排查方法篇

一、最原始的記憶體洩露測試

重複多次操作關鍵的可疑的路徑,從記憶體監控工具中觀察記憶體曲線,是否存在不斷上升的趨勢且不會在程式返回時明顯回落。
這種方式可以發現最基本,也是最明顯的記憶體洩露問題,對使用者價值最大,操作難度小,價效比極高。

二、MAT記憶體分析工具

2.1 MAT分析heap的總記憶體佔用大小來初步判斷是否存在洩露

在Devices 中,點選要監控的程式。
點選Devices檢視介面中最上方一排圖示中的“Update Heap”
點選Heap檢視
點選Heap檢視中的“Cause GC”按鈕
到此為止需檢測的程序就可以被監視。

Heap檢視中部有一個Type叫做data object,即資料物件,也就是我們的程式中大量存在的類型別的物件。在data object一行中有一列是“Total Size”,其值就是當前程序中所有Java資料物件的記憶體總量,一般情況下,這個值的大小決定了是否會有記憶體洩漏。可以這樣判斷:


進入某應用,不斷的操作該應用,同時注意觀察data object的Total Size值,正常情況下Total Size值都會穩定在一個有限的範圍內,也就是說由於程式中的的程式碼良好,沒有造成物件不被垃圾回收的情況。

所以說雖然我們不斷的操作會不斷的生成很多物件,而在虛擬機器不斷的進行GC的過程中,這些物件都被回收了,記憶體佔用量會會落到一個穩定的水平;反之如果程式碼中存在沒有釋放物件引用的情況,則data object的Total Size值在每次GC後不會有明顯的回落。隨著操作次數的增多Total Size的值會越來越大,直到到達一個上限後導致程序被殺掉。

2.2 MAT分析hprof來定位記憶體洩露的原因所在。
這是出現記憶體洩露後使用MAT進行問題定位的有效手段。
A)Dump出記憶體洩露當時的記憶體映象hprof,分析懷疑洩露的類:

B)分析持有此類物件引用的外部物件


C)分析這些持有引用的物件的GC路徑


D)逐個分析每個物件的GC路徑是否正常


從這個路徑可以看出是一個antiRadiationUtil工具類物件持有了MainActivity的引用導致MainActivity無法釋放。此時就要進入程式碼分析此時antiRadiationUtil的引用持有是否合理(如果antiRadiationUtil持有了MainActivity的context導致節目退出後MainActivity無法銷燬,那一般都屬於記憶體洩露了)。

2.3 MAT對比操作前後的hprof來定位記憶體洩露的根因所在。
為查詢記憶體洩漏,通常需要兩個 Dump結果作對比,開啟 Navigator History面板,將兩個表的 Histogram結果都新增到 Compare Basket中去
A) 第一個HPROF 檔案(usingFile > Open Heap Dump ).
B)開啟Histogram view.
C)在NavigationHistory view裡 (如果看不到就從Window >show view>MAT- Navigation History ), 右擊histogram然後選擇Add to Compare Basket .


D)開啟第二個HPROF 檔案然後重做步驟2和3.
E)切換到Compare Basket view, 然後點選Compare the Results (檢視右上角的紅色"!"圖示)。


F)分析對比結果


可以看出兩個hprof的資料物件對比結果。
通過這種方式可以快速定位到操作前後所持有的物件增量,從而進一步定位出當前操作導致記憶體洩露的具體原因是洩露了什麼資料物件。

注意:
如果是用 MAT Eclipse 外掛獲取的 Dump檔案,不需要經過轉換則可在MAT中開啟,Adt會自動進行轉換。
而手機SDk Dump 出的檔案要經過轉換才能被 MAT識別,Android SDK提供了這個工具 hprof-conv (位於 sdk/tools下)
首先,要通過控制檯進入到你的 android sdk tools 目錄下執行以下命令:
./hprof-conv xxx-a.hprof xxx-b.hprof
例如 hprof-conv input.hprof out.hprof
此時才能將out.hprof放在eclipse的MAT中開啟。

三、手機管家記憶體洩露每日監控方案

目前手機管家的記憶體洩露每日監控會自動執行並輸出是否存在疑似洩露的報告郵件,不論洩露物件的大小。這其中涉及的核心技術主要是AspectJ,MLD自研工具(原理是虛引用)和UIAutomator。

3.1AspectJ插樁監控程式碼
手機管家目前使用一個ant指令碼加入MLD的監控程式碼,並通過AspectJ的語法實現插樁。
使用AspectJ的原因是可以靈活分離出專案原始碼與監控程式碼,通過不同的編譯指令碼打包出不同用途的安裝測試包:如果測試包是經過Aspect插樁了MLD監控程式碼的話,那麼執行完畢後會輸出指定格式的日誌檔案,作為後續分析工作的資料基礎。

3.2MLD實現監控核心邏輯
這是手機管家內的一個工具工程,正式打包不會打入,BVT等每日監控測試包可以打入。打入後可以通過諸如addObject介面(通過反射去檢查是否含有該工具並呼叫)來加入需要監控的檢測物件,這個工具會自動在指定時機(如退出管家)去檢測該物件是否發生洩漏。

這個記憶體洩露檢測的基本原理是:
虛引用主要用來跟蹤物件被垃圾回收器回收的活動。虛引用必須和引用佇列(ReferenceQueue)聯合使用(在虛引用函式就必須關聯指定)。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,自動把這個虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。

基於以上原理,MLD工具在呼叫介面addObject加入監控型別時,會為該型別物件增加一個虛引用,注意虛引用並不會影響該物件被正常回收。因此可以在ReferenceQueue引用佇列中統計未被回收的監控物件是否超過指定閥值。

利用PhantomReferences(虛引用)和ReferenceQueue(引用佇列),當PhantomReferences被加入到相關聯的ReferenceQueue時,則視該物件已經或處於垃圾回收器回收階段了。

MLD監控原理核心

目前手機管家已對大部分類完成記憶體洩露的監控,包括各種activity,service和view頁面等,務求在技術上能帶給使用者最順滑的產品體驗。

接下來簡單介紹下這個工具的判斷核心。根據虛引用監控到的記憶體狀態,需要通過多種策略來判斷是否存在記憶體洩露。
(1)最簡單的方式就是直接在加入監控時就為該型別設定最大存在個數,舉個例子,各個DAO物件理論上只能存在最多一個,因此一旦出現兩個相同的DAO,那一般都是洩露了;
(2)第二種情況是在頁面退出程式退出時,檢索gc後無法釋放的物件列表,這些物件型別也會成為記憶體洩露的懷疑物件;
(3)最後一種情況比較複雜,基本原理是根據歷史操作判斷物件數量的增長幅度。根據物件的增長通過最小二乘法擬合出該物件型別的增長速度,如果超過經驗值則會列入疑似洩露的物件列表。

3.3 UIAutomator完成重複操作的自動化
最後一步就很簡單了。這麼多反覆的UI操作,讓人工來點就太浪費人力了。我們使用UIAutomator來進行自動化操作測試。
目前手機管家的每日自動化測試已覆蓋各個功能的主路徑,並通過配置檔案的方式來靈活驅動用例的增刪改查,最大限度保證了隨著版本推移用例的複用價值。

至此手機管家的記憶體洩露測試方案介紹完畢,也歡迎各路牛人交流溝通更多更強的記憶體洩露工具盒方案!


編者按下期精彩預告

解決問題還是要從源頭抓起,瞭解了記憶體洩露的排查方法,肯定還有很多開發者想弄清記憶體洩露發生的本因。彆著急,下週同一時間,Bugly將為大家推送記憶體洩露從入門到精通三部曲的下篇:記憶體洩露常見原因。敬請期待~