內存泄露從入門到精通三部曲之排查方法篇

內存泄露從入門到精通三部曲之排查方法篇

2015.11.11騰訊Bugly

1 最原始的內存泄露測試

重複多次操作關鍵的可疑的路徑,從內存監控工具中觀察內存曲線,是否存在不斷上升的趨勢且不會在程序返回時明顯回落。

這種方式可以發現最基本,也是最明顯的內存泄露問題,對用戶價值最大,操作難度小,性價比極高。

2 MAT內存分析工具

2.1 MAT分析heap的總內存佔用大小來初步判斷是否存在泄露

在Devices 中,點擊要監控的程序。

點擊Devices視圖界面中最上方一排圖標中的“Update Heap”

點擊Heap視圖

點擊Heap視圖中的“Cause GC”按鈕

到此爲止需檢測的進程就可以被監視。

1.1

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,分析懷疑泄露的類:

1.2

B)分析持有此類對象引用的外部對象

1.3

C)分析這些持有引用的對象的GC路徑

1.4

D)逐個分析每個對象的GC路徑是否正常

1.5

從這個路徑可以看出是一個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 .

1.6

D)打開第二個HPROF 文件然後重做步驟2和3.

E)切換到Compare Basket view, 然後點擊Compare the Results (視圖右上角的紅色”!”圖標)。

1.7

F)分析對比結果

1.8

可以看出兩個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中打開。

3 手機管家內存泄露每日監控方案

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

3.1 AspectJ插樁監控代碼

手機管家目前使用一個ant腳本加入MLD的監控代碼,並通過AspectJ的語法實現插樁。

使用AspectJ的原因是可以靈活分離出項目源碼與監控代碼,通過不同的編譯腳本打包出不同用途的安裝測試包:如果測試包是經過Aspect插樁了MLD監控代碼的話,那麼運行完畢後會輸出指定格式的日誌文件,作爲後續分析工作的數據基礎。

3.2 MLD實現監控核心邏輯

這是手機管家內的一個工具工程,正式打包不會打入,BVT等每日監控測試包可以打入。打入後可以通過諸如addObject接口(通過反射去檢查是否含有該工具並調用)來加入需要監控的檢測對象,這個工具會自動在指定時機(如退出管家)去檢測該對象是否發生泄漏。

這個內存泄露檢測的基本原理是:

虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用必須和引用隊列(ReferenceQueue)聯合使用(在虛引用函數就必須關聯指定)。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,自動把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。

基於以上原理,MLD工具在調用接口addObject加入監控類型時,會爲該類型對象增加一個虛引用,注意虛引用並不會影響該對象被正常回收。因此可以在ReferenceQueue引用隊列中統計未被回收的監控對象是否超過指定閥值。

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

1.9

MLD監控原理核心

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

接下來簡單介紹下這個工具的判斷核心。根據虛引用監控到的內存狀態,需要通過多種策略來判斷是否存在內存泄露。

(1)最簡單的方式就是直接在加入監控時就爲該類型設定最大存在個數,舉個例子,各個DAO對象理論上只能存在最多一個,因此一旦出現兩個相同的DAO,那一般都是泄露了;

(2)第二種情況是在頁面退出程序退出時,檢索gc後無法釋放的對象列表,這些對象類型也會成爲內存泄露的懷疑對象;

(3)最後一種情況比較複雜,基本原理是根據歷史操作判斷對象數量的增長幅度。根據對象的增長通過最小二乘法擬合出該對象類型的增長速度,如果超過經驗值則會列入疑似泄露的對象列表。

3.3 UIAutomator完成重複操作的自動化

最後一步就很簡單了。這麼多反覆的UI操作,讓人工來點就太浪費人力了。我們使用UIAutomator來進行自動化操作測試。

目前手機管家的每日自動化測試已覆蓋各個功能的主路徑,並通過配置文件的方式來靈活驅動用例的增刪改查,最大限度保證了隨着版本推移用例的複用價值。

至此手機管家的內存泄露測試方案介紹完畢,也歡迎各路牛人交流溝通更多更強的內存泄露工具盒方案
發佈了144 篇原創文章 · 獲贊 18 · 訪問量 74萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章