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

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

內(nèi)存泄漏基本概念

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

內(nèi)存泄漏主要原因就是一個生命周期長的對象陈轿,持有了一個生命周期短的對象的引用。這樣秦忿,會導致短的對象在該回收時候無法被回收麦射。Android中比較典型的有:1、靜態(tài)變量持有Activity的context灯谣。2潜秋、或者Handler持有某個組件的context,同時如果Looper的消息隊列中有針對該Handler的消息沒有被處理胎许,那么會被作為target持有強引用半等,最終的導致context無法釋放,導致相應組件在退出時無法被內(nèi)存回收呐萨。3杀饵、非靜態(tài)內(nèi)部類默認持有外部類的引用,這樣如果我們在Activity中定義了一個Thread內(nèi)部類谬擦,同時直接通過new Thread的方式去運行線程切距,那么在線程運行結束之前,線程都會持有Activity的引用惨远,從而導致Activity無法被釋放谜悟。

內(nèi)存檢測工具

LeakCananry

LeakCanary,主要監(jiān)測的是使用過程中Activity北秽,F(xiàn)ragment等組件是否沒被內(nèi)存回收葡幸。使用方法也十分簡單,相當于裝了一個監(jiān)聽器贺氓,然后通過正常 操作去尋找內(nèi)存泄漏蔚叨,發(fā)生內(nèi)存泄漏的時候會有Toast,同時可以在相應程序查看哪里發(fā)生內(nèi)存泄漏。?

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

當發(fā)生內(nèi)存泄漏時丹擎,屏幕會出現(xiàn)Toast,同時打開桌面上的Leaks程序歇父,顯示泄漏的內(nèi)存蒂培,如下圖:?

LeakCananry實現(xiàn)步驟大致是:?

實現(xiàn)大致步驟是:?

1、自動把activity加入到KeyedWeakReference?

2榜苫、在background線程中护戳,檢查onDestroy后reference是否被清除,且沒有觸發(fā)gc?

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

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

5晕讲、HeapAnalyzer在heap dump中根據(jù)reference key找到KeyedWeakReference乘综。?

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

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

方法的有點是簡單易行,但是只能檢測Activity欣舵、Fragment是否發(fā)生內(nèi)存泄漏擎鸠。

觀看整體內(nèi)存使用情況

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

使用adb shell,進入手機adb缘圈,執(zhí)行命令:

dumpsys meminfo <包名> [-參數(shù)]

可以查看應用不同部分內(nèi)存分配情況劣光。比如Java heap,Native heap等?

輸出是目前具體應用的內(nèi)存分配糟把,單位是kilobytes?

因為程序涉及jni绢涡,經(jīng)常會分配本地內(nèi)存,所以會使用adb shell 的方式去查看native heap的分配情況遣疯。

結果如下:

分析各個參數(shù):?

Private Clean/Dirty RAM:?

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

DDMS

使用流程

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

將手機通過USB鏈接至電腦敷待,鏈接時需要確認手機是處于“USB調(diào)試”模式,而不是作為“MassStorage”仁热;

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

點擊選中想要監(jiān)測的進程举哟,比如system_process進程;

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

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

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

如何檢測內(nèi)存泄漏秽褒?

Heap視圖中部有一個Type叫做dataobject壶硅,即數(shù)據(jù)對象,也就是我們的程序中實例化的對象销斟。在data object一行中有一列是“Total Size”庐椒,其值就是當前進程中所有Java數(shù)據(jù)對象的內(nèi)存總量,一般情況下蚂踊,這個值的大小決定了是否會有內(nèi)存泄漏扼睬。?

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

通過DDMS方式粪躬,DataObject 的totalSize如果穩(wěn)定在一個大概范圍內(nèi)担败,則可以確定沒有發(fā)生內(nèi)存泄漏。

MAT

然而镰官,并不是所有的內(nèi)存泄漏都十分明顯提前,并且會最終導致OOM。有時候只有幾個對象被泄漏泳唠,雖然影響不大狈网,但是無疑浪費了內(nèi)存。?

要發(fā)現(xiàn)這種比較隱蔽的內(nèi)存泄漏笨腥,我們需要使用MAT工具拓哺。?

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

支配樹

支配樹體現(xiàn)了對象實例間的支配關系士鸥,在對象引用圖中,所有指向?qū)ο驜的路徑都經(jīng)過對象A谆级,則認為對象A支配對象B烤礁。?


在這張圖里,左邊是對象引用關系肥照,對于A和B脚仔,要抵達這兩個點必須經(jīng)過GC root。而對于C可以從A也可以從B抵達建峭,但都必須經(jīng)過GC root玻侥,所以最近的支配點同樣也是GC root决摧。?

對于點D亿蒸,不管是從C->D還是C->D->F->D,都必須經(jīng)過的最近的點是C掌桩,所以C是D的支配點边锁。同理可得EFHG在支配樹中的位置。

SHALLOWHEAP和RETAINED HEAP

Shallow heap表示對象本身所占內(nèi)存大小波岛,一個內(nèi)存大小100bytes的對象Shallow heap就是100bytes茅坛。?

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

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

對象自身占用的內(nèi)存大小斥铺,不包括它引用的對象。?

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

針對數(shù)組類型的對象,它的大小是數(shù)組元素對象的大小總和剔交。?

Retained Size?

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

換句話說,Retained Size就是當前對象被GC后岖常,從Heap上總共能釋放掉的內(nèi)存驯镊。?

不過,釋放的時候還要排除被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上總共能釋放掉的內(nèi)存滨砍。

以上概念往湿,都是在使用MAT進行內(nèi)存分析經(jīng)常使用的,所以要記住惋戏。

MAT的下載與使用

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

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

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

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

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

使用方法:?

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

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

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

如下:

利用MAT分析內(nèi)存泄漏

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

在Histogram視圖中查找retained heap值最大的項李请,并分析這里是否發(fā)生內(nèi)存泄漏瞧筛。

注意,一般情況下我們忽略java导盅、android系統(tǒng)自帶的對象较幌,而著重分析我們自己程序中的對象。所以在上面輸入過濾Class Name白翻。

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

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

outcoming reference就是子節(jié)點阁苞,查看一些當前對象引用著的對象困檩。

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

實戰(zhàn)分析

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

1、使用DDMS查看內(nèi)存

使用DDMS的過程中甚牲,針對應用分別進行了多次檢測义郑,主要查看程序運行前的內(nèi)存使用情況和程序運行后的內(nèi)存使用情況:?

使用前:

使用后:?

通過上述數(shù)據(jù)可以看到,在程序運行前data object也就是在堆上分配的數(shù)據(jù)是180KB左右鳖藕,而運行后內(nèi)存大概在300KB上下浮動魔慷,沒有呈現(xiàn)一個明顯的一直上升的情況只锭,故而沒有明顯的內(nèi)存泄漏著恩,基本沒有導致OOM的可能。

但是蜻展,可以發(fā)現(xiàn)喉誊,程序運行一次以后,放置一段時間纵顾,即便手動觸發(fā)GC伍茄,堆上的內(nèi)存雖然回落,但是仍然是288KB施逾,與執(zhí)行前的180KB相差較大敷矫,說明有一些對象被GC roots引用例获,無法完成釋放。

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

2怎茫、使用MAT分析內(nèi)存轉儲

前面分析內(nèi)存使用發(fā)現(xiàn)收壕,使用前和使用后有一個100KB左右的差值,同時即便放置一段時間仍然無法使用轨蛤。將before和after的直方圖加入對比欄蜜宪,在MAT中進行對比:

點擊右上角的紅色嘆號:

對比發(fā)現(xiàn)兩個shallow heap大小基本相同,多出的部分是UpdatePartResultThread祥山,系統(tǒng)類而不是我們自己編寫程序造成的圃验。?

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

可以看出,程序執(zhí)行后缝呕,newActivity強引用了一些對象损谦,在newAcitivity沒有推出前,retainedheap部分內(nèi)存無法被回收岳颇。這也就是我們在DDMS中發(fā)現(xiàn)堆內(nèi)存差異的主要原因照捡。?

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

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

List objects:?

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

Incoming reference是父節(jié)點瞻鹏,即當前對象被誰引用悲立,為什么沒被回收。

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

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

排除系統(tǒng)的對象倔撞,還是主要分析我們編寫的程序讲仰。

最后發(fā)現(xiàn),我們在之前使用LeakCanary時痪蝇,注冊的相應監(jiān)聽器沒有回收鄙陡,發(fā)現(xiàn)了內(nèi)存泄漏 :)冕房。

去掉LeakCanary,再次測試發(fā)現(xiàn)data object的值確實下降了不少趁矾。

繼續(xù)分析毒费,發(fā)現(xiàn)newActivity引用了一個

致使一部分內(nèi)存無法被釋放。這個問題屬于客戶端實現(xiàn)問題愈魏,不在內(nèi)存泄漏的范圍內(nèi)觅玻。?

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

?

可以看到培漏,服務端的類大部分shallow heap都為0溪厘,也就是已經(jīng)被垃圾回收。

結論

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

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末冷冗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惑艇,更是在濱河造成了極大的恐慌蒿辙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滨巴,死亡現(xiàn)場離奇詭異思灌,居然都是意外死亡,警方通過查閱死者的電腦和手機恭取,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門泰偿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜈垮,你說我怎么就攤上這事耗跛。” “怎么了窃款?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵课兄,是天一觀的道長牍氛。 經(jīng)常有香客問我晨继,道長,這世上最難降的妖魔是什么搬俊? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任紊扬,我火速辦了婚禮蜒茄,結果婚禮上,老公的妹妹穿的比我還像新娘餐屎。我一直安慰自己檀葛,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布腹缩。 她就那樣靜靜地躺著屿聋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪藏鹊。 梳的紋絲不亂的頭發(fā)上润讥,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音盘寡,去河邊找鬼楚殿。 笑死,一個胖子當著我的面吹牛竿痰,可吹牛的內(nèi)容都是我干的脆粥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼影涉,長吁一口氣:“原來是場噩夢啊……” “哼变隔!你這毒婦竟也來了?” 一聲冷哼從身側響起蟹倾,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤弟胀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后喊式,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孵户,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年岔留,在試婚紗的時候發(fā)現(xiàn)自己被綠了夏哭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡献联,死狀恐怖竖配,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情里逆,我是刑警寧澤进胯,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站原押,受9級特大地震影響胁镐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一盯漂、第九天 我趴在偏房一處隱蔽的房頂上張望颇玷。 院中可真熱鬧,春花似錦就缆、人聲如沸帖渠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽空郊。三九已至,卻和暖如春切揭,著一層夾襖步出監(jiān)牢的瞬間渣淳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工伴箩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留入愧,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓嗤谚,卻偏偏與公主長得像棺蛛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子巩步,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容