使用MAT排查Android內存泄漏

前言:

MAT是Eclipse出品的一個插件,當然也有獨立的版本。下載地址:點我

在這裏先提醒一下:
1、本篇文章假定你已經可以拿到hprof文件,如果不懂這個文件,請自行查詢
2、MAT並不會準確地告訴我們哪裏發生了內存泄漏,而是會提供一大堆的數據和線索,我們需要根據自己的實際代碼和業務邏輯去分析這些數據,判斷到底是不是真的發生了內存泄漏。

hprof文件格式轉換

MAT僅支持對標準格式的hprof文件進行內存分析,所以,我們首先要使用AndroidSDK的platform-tools目錄下提供的hprof-conv.exe工具,先把Java heap文件轉成標準格式的hprof文件,步驟如下:
1、首先要獲取hprof文件,我們這裏假設名字叫做01.hprof
2、配置hprof-conv.exe工具類的環境變量
3、打開命令行窗口,進入到01.hprof文件所在的目錄
4、輸入命令行進行文件格式轉換:hprof-conv -z 01.hprof 02.hprof
5、成功得到02.hprof文件

初步使用MAT工具導入hprof文件

點擊左上角菜單欄中的file->open file 選擇02.hprof,操作成功之後,會出現一個類似下圖的頁面:
01

開始使用MAT工具導入hprof文件

MAT中提供了非常多的功能,這裏我們只要學習幾個最常用的就可以了。上圖那個餅狀圖展示了最大的幾個對象所佔內存的比例,這張圖中提供的內容並不多,我們可以忽略它。在這個餅狀圖的下方就有幾個非常有用的工具了。

Histogram:

直方圖,可以列出內存中每個對象的名字、數量以及大小。
00

Dominator Tree:

會將所有內存中的對象按大小進行排序,並且我們可以分析對象之間的引用結構。

1)Dominator Tree
02
從上圖可以看到右邊存在着3個參數。Retained Heap表示這個對象以及它所持有的其它引用(包括直接和間接)所佔的總內存,因此從上圖中看,前兩行的Retained Heap是最大的,分析內存泄漏時,內存最大的對象也是最應該去懷疑的。

另外大家應該可以注意到,在每一行的最左邊都有一個文件型的圖標,這些圖標有的左下角帶有一個紅色的點,有的則沒有。帶有紅點的對象就表示是可以被GC Roots訪問到的,
可以被GC Root訪問到的對象都是無法被回收的。那麼這就可以說明所有帶紅色的對象都是泄漏的對象嗎?當然不是,因爲有些對象系統需要一直使用,本來就不應該被回收。
如果發現有的對象右邊有寫着System Class,那麼說明這是一個由系統管理的對象,並不是由我們自己創建並導致內存泄漏的對象。

根據我們在Android studio的Java heap文件的提示,TestActivity對象有可能發生了內存泄漏,於是我們直接在上面搜TestActivity(這個搜索功能也是很強大的):

左邊的inspector可以查看對象內部的各種信息:
03

當然,如果你覺得按照默認的排序方式來查看不方便,你可以自行設置排序的方式:

  • Group by class
  • Group by class loader
  • Group by package
    04
    從上圖可以看出,我們搜出了3個TestActivity的對象,一般在退出某個activity後,就結束了一個activity的生命週期,應該會被GC正常回收纔對的。通常情況下,一個activity應該只有1個實例對象,但是現在居然有3個TestActivity對象存在,說明之前的操作,產生了3個TestActivity對象,並且無法被系統回收掉。
    接下來繼續查看引用路徑。

對着TestActivity對象點擊右鍵 -> Merge Shortest Paths to GC Roots(當然,這裏也可以選擇Path To GC Roots) -> exclude all phantom/weak/soft etc. references

爲什麼選擇exclude all phantom/weak/soft etc. references呢?因爲弱引用等是不會阻止對象被垃圾回收器回收的,所以我們這裏直接把它排除掉

05
接下來就能看到引用路徑關係圖了:
06
從上圖可以看出,TestActivity是被this$0所引用的,它實際上是匿名類對當前類的引用。this$0又被callback所引用,接着它又被Message中一串的next所引用…到這裏,我們就已經分析出內存泄漏的原因了,接下來就是去改善存在問題的代碼了。

2)Histogram
07
這裏是把當前應用程序中所有的對象的名字、數量和大小全部都列出來了,那麼Shallow Heap又是什麼意思呢?就是當前對象自己所佔內存的大小,不包含引用關係的。

上圖當中,byte[]對象的Shallow Heap最高,說明我們應用程序中用了很多byte[]類型的數據,比如說圖片。可以通過右鍵 -> List objects -> with incoming references來查看具體是誰在使用這些byte[]。

當然,除了一般的對象,我們還可以專門查看線程對象的信息:
08
Histogram中是可以顯示對象的數量的,比如說我們現在懷疑TestActivity中有可能存在內存泄漏,就可以在第一行的正則表達式框中搜索“TestActivity”,如下所示:
09

接下來對着TestActivity右鍵 -> List objects -> with outgoing references查看具體TestActivity實例

注:
List objects -> with outgoing references :表示該對象的出節點(被該對象引用的對象)
List objects -> with incoming references:表示該對象的入節點(引用到該對象的對象)

如果想要查看內存泄漏的具體原因,可以對着任意一個TestActivity的實例右鍵 -> Merge Shortest Paths to GC Roots(當然,這裏也可以選擇Path To GC Roots) -> exclude all phantom/weak/soft etc. references,如下圖所示:
10
11
從這裏可以看出,Histogram和Dominator Tree兩種方式下操作都是差不多的,只是兩種統計圖展示的側重點不太一樣,實際操作中,根據需求選擇不同的方式即可。

3)兩個hprof文件的對比
爲了排查內存泄漏,經常會需要做一些前後的對比。下面簡單說一下兩種對比方式:

1.直接對比

工具欄最右邊有個“Compare to another heap dump”的按鈕,只要點擊,就可以生成對比後的結果。(注意,要先在MAT中打開要對比的hprof文件,才能選擇對比的文件):
12
2.添加到campare basket裏對比

在window菜單下面選擇compare basket:
13
在文件的Histogram view模式下,在navigation history下選擇add to compare basket:
14
然後就可以通過 Compare Tables 來進行對比了:
15

總結

最後,還是要再次提醒一下,工具是死的,人是活的,MAT也沒有辦法保證一定可以將內存泄漏的原因找出來,還是需要我們對程序的代碼有足夠多的瞭解,知道有哪些對象是存活的,以及它們存活的原因,然後再結合MAT給出的數據來進行具體的分析,這樣纔有可能把一些隱藏得很深的問題原因給找出來。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章