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在內存分析中的使用就總結完畢,望紕漏指出童鞋們能夠不吝指出。

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