Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Studio和MAT結合使用來分析內存問題

Android Studio和MAT結合使用來分析內存問題

編輯:關於Android編程

Android開發中時常會遇到內存洩漏的問題,而Android系統對單個App又有一定的內存限制,此值可以通過一下方式獲取:

ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
int memoryClass = am.getMemoryClass();

上述代碼中momeryClass的值可以當做每個App的內存限制。這個值根據不同的設備廠商都是不一樣的,比如我的模擬器的值是32M,如果在我的模擬器上運行的一個App,分配的內存空間超過32M,則會報OOM(內存溢出)!而內存洩漏也是一個導致內存溢出的隱患,因此必須掌握解決內存溢出的方法。

本章主要講解使用Android Studio查看是否有內存洩漏問題,然後使用MAT(Memory Analyzer Tool)來分析並解決內存洩漏問題。

Android Studio分析是否有內存洩漏


打開Android Studio中的Android Monitor中的Memory面板,可以看到有一個實時變化的堆內存曲線圖,如下圖所示
這裡寫圖片描述

上圖中重點列出了3部分內容:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxjb2RlPrG7suLK1LXE1tW2y8no1sOjrMjnzbzL+cq+ztLKx9TaxKPE4sb3TmV4dXNfU8nP1/ay4srUILG7suLK1LXEvfizzKOsteO797/J0aHU8cbky/tBcHBsaWNhdGlvbrvy1d+9+LPMILWxx7Cxu7LiytS1xL34s8zW0MTatOa31sXkx+m/9jxiciAvPg0KQWxsb2NhdGVktPqx7dLRt9bF5LXEv9W85CBGcmVltPqx7b/J08PKo9Pgv9W85CBBbGxvY2F0ZWQgKyBGcmVlsrvE3LOsQXBwxNq05s/e1sYoMzJNKSA8L2NvZGU+DQo8cD48Y29kZT7E2rTmt9bO9rXEuaS+38C4o6y008nPz/LPwtK7ubI0uPawtMWlo6zSwLTOysejujxiciAvPg0KPGltZyBhbHQ9"Enable" src="/uploadfile/Collfiles/20160811/20160811100327967.png" title="\" /> 終止檢測的開關,沒什麼實質性的作用

InitGC 就是手動調用GC,我們在抓內存前,一定要手動點擊 Initiate GC按鈕手動觸發GC,這樣抓到的內存使用情況就是不包括Unreachable對象的(Unreachable指的是可以被垃圾回收器回收的對象,但是由於沒有GC發生,所以沒有釋放,這時抓的內存使用中的Unreachable就是這些對象)

DumpHeap 獲取hprof文件(hprof文件是我們使用MAT工具分析內存時使用的文件),但這裡直接產生的文件MAT還不能直接使用,需用轉換成標准的hprof文件。可以使用AndroidStudio轉換或者用hprof-conv命令轉化,網上可以查到

AllocTrack 開始分配追蹤,第一次點擊可以指定追蹤內存的開始位置,第二次點擊可以結束追蹤的位置。這樣我們截取了一段要分析的內存,等待幾秒鐘AndroidStudio會給我們打開一個Allocation視圖(感覺和MAT工具差不多,不過MAT工具更加強大,我們也可以獲取hprof文件,使用MAT來分析)


 

寫一段代碼動態演示一下:

xml布局文件如下,定義一個Button,並設置onClick屬性


Activity代碼如下:聲明Button被點擊回調的click方法

public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }
}

通過上面代碼,可以預見,每次點擊Button時,都會動態生成10000個ImageView並添加到List中保存起來,Memory的效果圖如下:
Memory2
可以看到,剛開始系統分配了2M左右的內存,當點擊一次Button之後,內存增加到8M,再次點擊內存增加到24M左右。
上述情況下,當我們按下返回退出Activity時,然後點擊Init GC按鈕執行垃圾回收操作,進程中的內存會重新回到2M,如下圖:
Memory3
這種情況下,代碼是安全穩定的代碼,但是如果Activity中有內存洩漏會是何種情況呢,接下來我們先把之前的代碼修改一下,認為構造一個內存洩漏的場景,如下代碼所示:

public class MainActivity extends AppCompatActivity {

    private List list = new ArrayList<>();

    static MemoryLeak memoryLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (memoryLeak == null) {
            memoryLeak = new MemoryLeak();
        }

    }

    public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }
    }

    class MemoryLeak {
        void doSomeThing() {
            System.out.println("Wheee!!!");
        }
    }

可以看到,在MainActivity中,添加了一個非靜態內存類MemoryLeak,然後聲明了一個靜態MemoryLeak引用。
運行上述代碼,然後再次執行點擊Button的操作,可以看到內存同樣會上升到8M左右,再次點擊上升到16M左右,但是此時按下返回按鈕並執行垃圾回收操作之後,Allocated + Free的總空間並沒有重新回到2M左右,而是一直徘徊於8M左右 說明存在內存洩漏!!! 但是為什麼會是8M呢??

Android Studio生成內存字節文件


剛才在介紹Studio的Memory面板時,有提到一個工具欄Dump Java Heap,通過點擊此按鈕就可以導出一個hprof文件,此過程會比較慢,需要耐心等待,當下圖中心的圓圈停止轉動之後hprof文件也就導出成功
這裡寫圖片描述

導出完成後將自動打開這個文件,如下圖所示:
hprof
點擊Analyzer Tasks右邊的綠色運行箭頭,Android Studio會自動的根據此hprof文件分析有哪些類是有內存洩漏的,如下圖所示:
mmm
確實有一個MainActivity存在內存洩漏的情況,但是跟我之前預想的有一點出入,本來以為向網上很多人說的那樣,每次打開一個MainActivity時都會造成內存洩漏,但是現在事實就擺在眼前。仔細想了一下也恍然大悟了,MemoryLeak在第一個MainActivity中被聲明是static靜態的,當第二個被打開的MainActivity並不會再重新初始化MemoryLeak對象了,因此static MemoryLeak對象在內存中只是持有了第一個MainActivity的對象的引用,因此當我們調用多次GC操作之後,實際上只有第一個MainActivity不會被GC回收掉!!

如果再將Activity的代碼修改一下

package material.danny_jiang.com.adbmemoryanalyze;

import android.app.ActivityManager;
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private List list = new ArrayList<>();

    //private static MemoryLeak memoryLeak;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //memoryLeak = new MemoryLeak();

    }

    public void click(View view) {
        for (int i = 0; i < 10000; i++) {
            ImageView imageView = new ImageView(this);
            list.add(imageView);
        }

        new Thread() {
            @Override
            public void run() {
                super.run();
                while (true) {
                    try {
                        System.out.println("Thread running!!");
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

    class MemoryLeak {
        void doSomeThing() {
            System.out.println("Wheee!!!");
        }
    }
}

可以看到,我講造成內存洩漏的場景由內部類改成了內部線程類,並且在線程中無限循環打印log。

再次執行進入MainActivity–返回鍵–進入MainActivity–返回鍵的操作

然後再生成hprof文件並打開,並執行Analyzer Tasks,可以看到如下圖片的信息:
yyy
上圖可以看出打開的每一個MainActivity都會造成內存洩漏。 擦嘞!!為什麼這會兒又是這種情況呢???這個問題就牽涉到Java中線程的問題了—Java中的Thread有一個特點就是她們都是直接被GC Root所引用,也就是說Dalvik虛擬機對所有被激活狀態的線程都是持有強引用,導致GC永遠都無法回收掉這些線程對象,除非線程被手動停止並置為null或者用戶直接kill進程操作。看到這相信你應該也是心中有答案了吧 : 我在每一個MainActivity中都創建了一個線程,此線程會持有MainActivity的引用,即使退出Activity當前線程因為是直接被GC Root引用所以不會被回收掉,導致MainActivity也無法被GC回收。所以當使用線程時,一定要考慮在Activity退出時,及時將線程也停止並釋放掉

MAT內存分析工具


正常來講,根據上面我講的使用Studio來分析簡單的內存洩漏已經足夠了,但是在Studio之前有一款更加強大的內存分析工具MAT,谷歌工程師稱它更加的Powerful!!。接下來就看一下如何使用MAT來分析內存問題

1 首先在eclipse官網下載MAT工具

https://www.eclipse.org/mat/

2 下載完MAT並安裝好之後,需要先生成hprof文件。

這兩我還是使用之前線程造成內存洩漏的案例來演示,

首先第一次打開MainActivity時,點擊dump heap生成一個hprof文件 其次進行一系列的操作, 比如點擊Button,按下返回鍵,再次進入MainActivity等等,這裡我重復了4遍如上操作,然後再點擊dump heap生成hprof文件

3 點擊Studio的Captures欄,顯示剛才生成的hprof文件,如下圖所示:

two_hprof
這兩個文件我們需要使用MAT去打開並對比分析,但是MAT不能直接打開這兩個文件,需要將它轉換成MAT能夠識別的文件,Captures欄中,右鍵點擊每一個hprof文件,然後選擇Export to standard .hprof並保存到電腦目錄中,如下圖:
export_hprof

4 使用MAT打開轉換後的hprof文件,顯示如下圖

mat
可以看到有兩個dump的面板,其中每一個都顯示了一個內存的餅狀圖。其中用的最多的功能是左下角的Histogram, 點擊 Actions下的 Histogram項將得到 Histogram結果:
histogram
它按類名將所有的實例對象列出來,可以點擊表頭進行排序,在表的第一行可以輸入正則表達式來匹配結果 :
reg
在Histogram中,可以右鍵某一想查看的對象,然後選中List Objects來查看此對象的所有實例,如下圖
histogram_cat

選中之後,會跳出所有實例對象面板,在此面板中可以可以繼續某一特定實例在內存中的Path To GC Root(從GC開始的強引用)。在之前的案例操作中,我重復的進入MainActivity4次,並依次點擊Button運行線程,因此正常來說MainActivity應該有4個實例在內存當中,如下圖

exclude

exclude all phantom/weak/soft的意思是講所有的虛引用/軟引用/弱引用都排除掉,因為只有強引用才會造成內存洩漏!點擊之後顯示下圖信息:

path

可以看到,MainActivity最終都是被一個叫做MainActivity1的對象引用,而MainActivity1就是在click方法中創建的匿名內部類Thread對象。 最終我們找到了內存洩漏的根本原因 : 當Activity退出之後,Thread因為被GC Root直接引用,所以不會被GC回收掉,而Thread又持有Activity的引用導致Activity也無法被GC正常回收掉,造成了Activity的內存洩漏,大功告成!!!

如何發現內存洩漏


上面分別介紹了使用Android studio和MAT分析內存的方法。Android studio自帶的內存分析工具直觀方便,但其功能卻不如MAT強大,特別是沒有有效的搜索、排序等功能。遇到一些棘手的問題,可能還是要借助MAT來分析內存。

上面的例子是我們人為制造了一個內存洩漏,然後有意用工具檢測他。但實際開發中,我們如何發現內存洩漏呢?我想可以首先使用studio自帶或DDMS中的heap分析工具,觀察在反復執行某個操作時(例如打開某個頁面、點擊某個按鈕、加載某個資源等等)時,內存在執行GC後能始終維持在穩定的值附近。如果內存呈線性增長的趨勢,那一定是發生了內存洩漏。此時,就要dump出內存鏡像,然後使用工具分析了。

在分析內存時,第一是可以使用工具自帶的洩漏檢查器幫助定位。另外,可以在執行操作(懷疑造成內存洩漏的操作)前後,分別dump出一份內存鏡像,然後使用MAT的Compare Basket對比兩個文件的內存情況,這樣可以幫助定位到是哪個對象發生了洩漏。然後再找到這個對象的GC Roots,這樣就可以進一步定位到具體的代碼了。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved