1. 程式人生 > >MAT在記憶體分析中的簡單使用

MAT在記憶體分析中的簡單使用

在Android開發過程中,經常會遇到各種記憶體洩漏和記憶體溢位的問題,所謂的記憶體洩漏是指部分已經不再使用的變數還繼續佔用記憶體得不到及時釋放,而記憶體溢位則是指Android虛擬機器會給每個應用(對應一個程序)可分配的記憶體是有限的,當該應用佔用的記憶體達到可分配的最大記憶體時,應用繼續申請記憶體,這是就會出現記憶體溢位。記憶體溢位多是記憶體洩漏導致的,記憶體洩漏和記憶體溢位都會降低應用執行效率,導致應用卡頓,所以在日常開發中應該竟可能避免記憶體洩漏和記憶體溢位。

分析記憶體洩漏的方法很多,比如可以直接檢查程式碼檢視是否有在Activity的非靜態記憶體類或匿名記憶體類執行耗時操作,是否在使用非靜態的Handler中執行耗時操作,是否在Activity中註冊BroadCast Receiver然後沒有及時釋放,是否有強引用圖片沒有及時釋放,是否申請了IO變數而沒有手動釋放等,另外也可以通過LeakCanary工具整合到應用中來debug出記憶體洩漏原因。然而這些都不是本文想說的,本文就是想簡單的介紹下就Android Studio結合MAT(memery analizer tool)在分析記憶體洩漏時的簡單應用。

這裡在介紹MAT分析記憶體之前,先說說對應每個應用的三個記憶體引數

maxMemory:Andriod虛擬機器所能夠分配給應用的最大記憶體;
totalMemory:當前記憶體已經獲得的記憶體值;
freeMemory:應用當前已使用的記憶體值;

當用用的當前已經佔用的記憶體,達到totalMemory的一定比例後,虛擬機器就給應用再分配一定的記憶體,即應用的totalMemery會變大,totalMemery的最大值就是maxMemery
這三個引數可通過如下方式獲取

Runtime.getRuntime().maxMemory();
Runtime.getRuntime
().totalMemory(); Runtime.getRuntime().freeMemory();

前面說好了些基本的概念,下面就來說說Android Studio如何檢測記憶體使用,並且獲取可以用來分析記憶體的hprof檔案。

1、Android Studio的MEMORY工具

這裡通過寫一個LeakActivity,然後通過Android Studio的MEMORY工具來檢測當前的記憶體使用情況,LeakActivity程式碼如下

public class LeakActivity extends Activity {
    private ArrayList<String> stringList = new
ArrayList<String>(); private ArrayList<Bitmap> bitmapList = new ArrayList<Bitmap>(); private String str = "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii" + "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_leak); initBitmapData(); //initStringData(); } private void initStringData(){ for(int i = 0;i < 5000;i++){ stringList.add(str); } } private void initBitmapData(){ for(int i = 0;i < 3000;i++){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = 1; Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher,options); bitmapList.add(bitmap); } } public void onHandler(View iew){ Toast.makeText(LeakActivity.this,"onHandler Clicked",Toast.LENGTH_SHORT).show(); for(int i = 0;i < 20;i++){ Bundle bundle = new Bundle(); bundle.putString("name","yoryky"); bundle.putInt("age",15); Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher); bundle.putParcelable("image",bitmap); Message message = new Message(); message.what = 1; message.obj = bundle; handler.sendMessageDelayed(message,1000*10); } } private Handler handler= new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); } }; }

首先執行應用,然後由Android Profiler-> MEMORY開啟記憶體工具,然後就可以看到記憶體使用情況了,操作應用,由MainAcivity跳轉到這裡的LeakActivity,點選觸發onHandler方法,然後促使handler開發傳送延時訊息,然後我們再返回MainActivity,我們看這時的MEMORY的記憶體情況
這裡寫圖片描述
點選圖片中的Dump Java Heap可獲取當前的Java Heap使用情況,也可以匯出leak3.hprof檔案,供之後MAT來分析。
這裡從MEMORY分析工具,我們就可以清晰的看到在頁面已經返回MainActivity後,LeakActivity本來應該銷燬了,但是這是獲取到的頁面居然有LeakActivity,同時還有兩個MessageQueue,以及很多Message物件(有一個MessageQueue,以及一些Message是正常的,因為主執行緒本身持有一個MessageQueue來處理訊息),這是我們就可以初步推斷出是LeakActivity頁面產生了記憶體洩漏了。同時,這裡可以看到Bitmap佔用了很大的的空間,這裡佔用的記憶體空間有兩個衡量值,一個是Shallow Size,另一個是Retained Size這裡也說說吧

Shallow Size: 指物件本身所佔用的記憶體空間大小,不包含其所引用的物件所佔的記憶體空間;
Retained Size: 指物件本身以及其所引用的物件所佔記憶體空間大小;

同時,我們這裡看到Bitmap物件,所佔用的記憶體空間是Native型別的記憶體空間(關於記憶體空間型別這裡就不討論了),這個需要注意。Android在3.X之前Bitmap是放在Native Heap中的,之後版本放到Java Heap中,8.X之後版本又放到Native Heap中,放在Native Heap的值實際上是不算在應用的maxMemory中的,同事在Native Heap中的Bitmap,下面要講的MAT工具中的也檢測不到的,也就是說Bitmap的記憶體洩漏,單靠MAT的記憶體分析,是分析不到,這個要結合這裡的Android Studio的MEMORY結合使用來判斷。

這一小節,我們知道了Android Studio中MEMORY工具的使用,並且獲取了一個leak3.hprof的記憶體資料,實際上這個檔案可以直接拉到Android Studio中,進行分析,童鞋們可以自己試試。

2、MAT的簡單使用

上一小結獲取到了一個leak3.hprof檔案,是否可以直接用MAT開啟使用呢?答案是否定的,MEMEORY工具獲取到的hprof檔案和MAT能夠分析的hprof格式不一樣,需要通過sdk的platform-tools中的hprof-conv工具,作一次轉換
這裡寫圖片描述

通過轉換後,這裡得到MAT能夠識別的leak_conv3.hprof檔案,然後通過MAT開啟該檔案,通過Leak Suspects分析出下面的餅狀圖
這裡寫圖片描述

這個餅狀圖給出了3個可能的記憶體洩漏點,注意這個只是指出了存在記憶體洩漏的可能,而不是表示一定出現了記憶體洩漏,這裡開啟Problem Suspect1的Details看看
這裡寫圖片描述

這裡看到有String物件出現記憶體洩漏,應該對應LeakActivity頁面中str變數(LeakActivity沒有釋放,所以全域性變數str也沒有釋放)。

這個是MAT的Leak Suspects工具的使用,再看看Histogram工具的使用
這裡寫圖片描述

這裡我們看看android.graphics.Bitmap物件的詳情,通過右鍵 -> Merge Shortest Paths to GC Roots -> exclude all phantom/weak/soft etc. refrences 然後可以如下圖所示看到,原來這個Bitmap物件,有一部分是LeakActivity中的3000ge Bitmap物件沒有得到釋放,同時這裡也應該注意到這裡的Bitmap的Retained Heap值居然只有48也就是,MAT統計出的Bitmap所佔用的記憶體空間,沒有算其實際佔用的記憶體空間大小,只是算了其引用所佔的大小,這個一定要注意。
這裡寫圖片描述

這裡還可以看看Histogram的另一中使用方式,就是比較一個頁面不同狀態時的記憶體差別,我們在開啟LeakActivity時,不觸犯onLeak方法,然後返回MainActiviy重新抓取一個leak1.hprof,同樣的轉為MAT能識別的leak_conv1.hprof檔案,然後用MAT開啟
這裡寫圖片描述

點選上面的柱形圖示,然後點選頁面下部的histogram,右鍵Add to Compare Basket,同樣的方法對leak_conv3.hprof也用以一次,這樣Compare Basket就用兩個檔案了
這裡寫圖片描述

點選紅色感嘆號就可以完成對比了,從對比圖中,可以發現leak_conv3.hprof比leak_con1.hprof的Bitmap物件要多3000多個,這個也比價容易發現記憶體洩漏點
這裡寫圖片描述

到這裡就比較簡單的介紹了下MAT在記憶體分析中的使用,主要介紹了其Leak Suspects以及Histogram這兩個方法的使用。

3、使用命令獲取hprof檔案

可能有些童鞋要說,我如果是用Eclispe呢,我如果不想安裝Android Studio呢,有沒有其她辦法獲取hprof檔案呢?這裡提供一個adb命令來獲取hprof檔案的方法,這裡以這裡的com.example.test包名的模組為例來說明,注意在具體使用中替換為自己的包名
首先,adb shell進入shell命令,獲取模組程序id

ps -A | grep com.example.test

這裡寫圖片描述

程序id為18337,然後通過如下命令在手機對應目錄下生成hprof檔案

am dumpheap 18337 /data/local/tmp/leak.hprof

最後,退出shell命令後,通過如下命令可將對應hproft檔案拉去到電腦中來

adb pull /daa/local/tmp/leak.hprof

到此為止,本文淺薄的對MAT在記憶體分析中的使用就總結完畢,望紕漏指出童鞋們能夠不吝指出。