(轉載)詳解Android內存泄漏檢測與MAT使用

原文鏈接:https://www.jb51.net/article/100837.htm

內存泄漏基本概念

內存檢測這部分,相關的知識有JVM虛擬機垃圾收集機制,類加載機制,內存模型等。編寫沒有內存泄漏的程序,對提高程序穩定性,提高用戶體驗具有重要的意義。因此,學習Java利用java編寫程序的時候,要特別注意內存泄漏相關的問題。雖然JVM提供了自動垃圾回收機制,但是還是有很多情況會導致內存泄漏。?

內存泄漏主要原因就是一個生命周期長的對象,持有了一個生命周期短的對象的引用。這樣,會導致短的對象在該回收時候無法被回收。Android中比較典型的有:1、靜態變量持有Activity的context。2、或者Handler持有某個組件的context,同時如果Looper的消息隊列中有針對該Handler的消息沒有被處理,那么會被作為target持有強引用,最終的導致context無法釋放,導致相應組件在退出時無法被內存回收。3、非靜態內部類默認持有外部類的引用,這樣如果我們在Activity中定義了一個Thread內部類,同時直接通過new Thread的方式去運行線程,那么在線程運行結束之前,線程都會持有Activity的引用,從而導致Activity無法被釋放。

內存檢測工具

LeakCananry

LeakCanary,主要監測的是使用過程中Activity,Fragment等組件是否沒被內存回收。使用方法也十分簡單,相當于裝了一個監聽器,然后通過正常 操作去尋找內存泄漏,發生內存泄漏的時候會有Toast,同時可以在相應程序查看哪里發生內存泄漏。?

方法比較簡單,添加leakcanary依賴以后,新建一個Application入口,在Oncreate方法中安裝Leakcanary即可。

當發生內存泄漏時,屏幕會出現Toast,同時打開桌面上的Leaks程序,顯示泄漏的內存,如下圖:?

LeakCananry實現步驟大致是:?

實現大致步驟是:?

1、自動把activity加入到KeyedWeakReference?

2、在background線程中,檢查onDestroy后reference是否被清除,且沒有觸發gc?

3、如果reference沒有被清除,則dump heap到一個hprof文件并保存到app文件系統中?

4、在一個單獨進程中啟動HeapAnalyzerService,HeapAnalyzer使用HAHA來分析heap dump。?

5、HeapAnalyzer在heap dump中根據reference key找到KeyedWeakReference。?

6、HeapAnalyzer計算出到GC Roots的最短強引用路徑來判斷是否存在泄露,然后build出造成這個泄露的引用鏈。?

7、結果被傳回來app進程的DisplayLeakService,并展示一個泄露的notification。?

方法的有點是簡單易行,但是只能檢測Activity、Fragment是否發生內存泄漏。

觀看整體內存使用情況

詳情參見官方文檔:?https://developer.android.com/studio/profile/investigate-ram.html#ViewingAllocations?

使用adb shell,進入手機adb,執行命令:

dumpsys meminfo <包名> [-參數]

可以查看應用不同部分內存分配情況。比如Java heap,Native heap等?

輸出是目前具體應用的內存分配,單位是kilobytes?

因為程序涉及jni,經常會分配本地內存,所以會使用adb shell 的方式去查看native heap的分配情況。

結果如下:

分析各個參數:?

Private Clean/Dirty RAM:?

這部分內存是app的私有內存,當app銷毀是操作系統可以回收到的內存。其中private dirty只能被你的進程使用,同時只能存在在內存當中,當內存不夠,也不能通過分頁技術存儲到硬盤(操作系統相關知識),dalvik和native heap上的分配都是private dirty RAM。因為是dalvik heap和native heap共享的內存,所以命名dirty?

DDMS

使用流程

啟動eclipse后,切換到DDMS透視圖,并確認Devices視圖、Heap視圖都是打開的;

將手機通過USB鏈接至電腦,鏈接時需要確認手機是處于“USB調試”模式,而不是作為“MassStorage”;

鏈接成功后,在DDMS的Devices視圖中將會顯示手機設備的序列號,以及設備中正在運行的部分進程信息;

點擊選中想要監測的進程,比如system_process進程;

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

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

此時在Heap視圖中就會看到當前選中的進程的內存使用量的詳細情況。

如何檢測內存泄漏?

Heap視圖中部有一個Type叫做dataobject,即數據對象,也就是我們的程序中實例化的對象。在data object一行中有一列是“Total Size”,其值就是當前進程中所有Java數據對象的內存總量,一般情況下,這個值的大小決定了是否會有內存泄漏。?

正常情況下Total Size值都會穩定在一個有限的范圍內,也就是說沒有造成對象不被垃圾回收的情況,所以說雖然我們不斷的操作會不斷的生成很多對象,而在虛擬機不斷的進行GC的過程中,這些對象都被回收了,內存占用量會會落到一個穩定的水平。如果代碼中存在沒有釋放對象引用的情況,則dataobject的Total Size值在每次GC后不會有明顯的回落,隨著操作次數的增多Total Size的值會越來越大

通過DDMS方式,DataObject 的totalSize如果穩定在一個大概范圍內,則可以確定沒有發生內存泄漏。

MAT

然而,并不是所有的內存泄漏都十分明顯,并且會最終導致OOM。有時候只有幾個對象被泄漏,雖然影響不大,但是無疑浪費了內存。?

要發現這種比較隱蔽的內存泄漏,我們需要使用MAT工具。?

在了解支配樹之前,要先了解一些相關概念。

支配樹

支配樹體現了對象實例間的支配關系,在對象引用圖中,所有指向對象B的路徑都經過對象A,則認為對象A支配對象B。?


在這張圖里,左邊是對象引用關系,對于A和B,要抵達這兩個點必須經過GC root。而對于C可以從A也可以從B抵達,但都必須經過GC root,所以最近的支配點同樣也是GC root。?

對于點D,不管是從C->D還是C->D->F->D,都必須經過的最近的點是C,所以C是D的支配點。同理可得EFHG在支配樹中的位置。

SHALLOWHEAP和RETAINED HEAP

Shallow heap表示對象本身所占內存大小,一個內存大小100bytes的對象Shallow heap就是100bytes。?

Retained heap表示通過回收這一個對象總共能回收的內存,比方說一個100bytes的對象還直接或者間接地持有了另外3個100bytes的對象引用,回收這個對象的時候如果另外3個對象沒有其他引用也能被回收掉的時候,Retained heap就是400bytes。?

在使用mat進行分析時,我們常常接觸到的數據就是shallow size和retained size: Shallow Size?

對象自身占用的內存大小,不包括它引用的對象。?

針對非數組類型的對象,它的大小就是對象與它所有的成員變量大小的總和。當然這里面還會包括一些java語言特性的數據存儲單元。?

針對數組類型的對象,它的大小是數組元素對象的大小總和。?

Retained Size?

Retained Size=當前對象大小+當前對象可直接或間接引用到的對象的大小總和。(間接引用的含義:A->B->C, C就是間接引用)?

換句話說,Retained Size就是當前對象被GC后,從Heap上總共能釋放掉的內存。?

不過,釋放的時候還要排除被GC Roots直接或間接引用的對象。他們暫時不會被回收。如下圖:?

A對象的Retained Size=A對象的Shallow Size?

B對象的Retained Size=B對象的Shallow Size + C對象的Shallow Size?

因為B對象被釋放時,C同時被釋放,而D由于被GC roots直接引用所以不會被釋放。而Retained Size就是當前對象被GC后,從Heap上總共能釋放掉的內存。

以上概念,都是在使用MAT進行內存分析經常使用的,所以要記住。

MAT的下載與使用

下載地址:https://eclipse.org/mat/downloads.php?

這里沒有作為eclipse插件的方式下載mat,而是通過下載單獨的軟件客戶端。?

首先,在DDMS中選擇要檢測的進程并dump HPROF file,如下圖:?

HPROF中存儲的是當前內存的快照,因此,在dump快照之前先點擊cause GC手動觸發一次垃圾回收,這樣可以避免軟引用、弱引用等不必要的對象保留在內存中影響我們的分析。

轉儲出來的hprof文件,還有使用sdk自帶工具進行一下格式轉化,工具在sdk路徑下的platform-tools下,名稱為hprof-conv。

使用方法:?

/.hprof-conv.exe a.hprof b.hprof?

a 是輸入hprof文件名,b是輸出文件名。?

然后將b.hprof在eclipse memory Analyzer中打開,注意要轉換格式,不然無法成功打開。?

如下:

利用MAT分析內存泄漏

分析過程中,主要使用的是Histogram直方圖,和Dominater tree支配樹。

在Histogram視圖中查找retained heap值最大的項,并分析這里是否發生內存泄漏。

注意,一般情況下我們忽略java、android系統自帶的對象,而著重分析我們自己程序中的對象。所以在上面輸入過濾Class Name。

Retained heap表示因為這個對象,會導致多少對象無法回收。

右擊相應類,list objects->with incoming references。表明引用這個類的某個實例的其它類,也就是它在引用樹中的父節點。通過分析該對象被誰引用,來判斷為何沒被垃圾回收。?

outcoming reference就是子節點,查看一些當前對象引用著的對象。

此外看,Merge shortest path to gc root,可以找到一條到GC root的最短路徑,來看為什么當前對象無法被回收。

實戰分析

下面記錄了本人對一個項目的具體分析過程,以及各個工具的使用方法。

1、使用DDMS查看內存

使用DDMS的過程中,針對應用分別進行了多次檢測,主要查看程序運行前的內存使用情況和程序運行后的內存使用情況:?

使用前:

使用后:?

通過上述數據可以看到,在程序運行前data object也就是在堆上分配的數據是180KB左右,而運行后內存大概在300KB上下浮動,沒有呈現一個明顯的一直上升的情況,故而沒有明顯的內存泄漏,基本沒有導致OOM的可能。

但是,可以發現,程序運行一次以后,放置一段時間,即便手動觸發GC,堆上的內存雖然回落,但是仍然是288KB,與執行前的180KB相差較大,說明有一些對象被GC roots引用,無法完成釋放。

下面采用MAT工具進行進一步分析。在上面的過程中,轉出了三個hprof文件,將hprof文件利用Android sdk tools下的工具進行格式轉換,進行對比分析:

2、使用MAT分析內存轉儲

前面分析內存使用發現,使用前和使用后有一個100KB左右的差值,同時即便放置一段時間仍然無法使用。將before和after的直方圖加入對比欄,在MAT中進行對比:

點擊右上角的紅色嘆號:

對比發現兩個shallow heap大小基本相同,多出的部分是UpdatePartResultThread,系統類而不是我們自己編寫程序造成的。?

再看一下使用前后直方圖中的retained heap:

可以看出,程序執行后,newActivity強引用了一些對象,在newAcitivity沒有推出前,retainedheap部分內存無法被回收。這也就是我們在DDMS中發現堆內存差異的主要原因。?

右擊直方圖中的NewActivity,可以看見如下選項:

用的比較多的是List objects和Merger shortest Paths to GC Roots。?

List objects:?

Outgoing reference是支配樹中當前對象的子節點,也就是當前對象持有哪些引用。?

Incoming reference是父節點,即當前對象被誰引用,為什么沒被回收。

Merger shortest Paths to GC Roots:找到當前無法被釋放的對象到GC roots的最短路徑。即排查當前對象被誰引用,為什么沒有被釋放。這里因為我們的對象是一個Activity,當它顯示在前臺的時候,不會被垃圾回收,所以不是我們分析的點。

在這里,我們查看outgoing reference,查看當前對象擁有哪些強引用:

排除系統的對象,還是主要分析我們編寫的程序。

最后發現,我們在之前使用LeakCanary時,注冊的相應監聽器沒有回收,發現了內存泄漏 :)。

去掉LeakCanary,再次測試發現data object的值確實下降了不少。

繼續分析,發現newActivity引用了一個

致使一部分內存無法被釋放。這個問題屬于客戶端實現問題,不在內存泄漏的范圍內。?

接下來,在直方圖中過濾出服務端的類:

?

可以看到,服務端的類大部分shallow heap都為0,也就是已經被垃圾回收。

結論

在使用MAT分析內存時,最關鍵的就是找引用關系。如果一個應該被釋放的對象沒有被釋放,那么我們往往要查看它的incoming reference,看看是誰持有了它的強引用。同時利用Merger shortest GC roots找到到GC root的最短路徑,確定是由于被誰引用而導致無法GC。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容