Ruily blog

more reading more happiness

Ruily Zhu's avatar Ruily Zhu

android内存优化工具篇之MAT

众所周知android应用是基于JAVA语言开发的,JAVA有自己的垃圾回收机制,因此我们不用再释放内存、资源回收方面花费太多的精力,但是JAVA的GC(Garbage Collection)机制是自动进行的,因此在APP开发当中难免会遇到内存泄露,而android分配给每个应用的内存大小是有限制的因此大量的内存泄露积少成多会造成内存溢出(out of memory).所以我们应当重视内存优化问题.接下来我将通过Studio创建一个简单的单例模式内存泄露的例子,使用MAT(Eclipse MemoryAnalyzer)来进行分析.

存放基本数据类型以及对象的引用地址(指向堆里具体的对象),存储局部变量和方法调用,不负责存储对象,每一个
线程都对应一个栈内存,栈内存只对其所属线程可见,如果没有可用的栈内存会抛出StackOverFlowError异常.

为对象开辟内存空间,保存对象,GC负责管理这里的内存回收,对所属进程全部可见,没有可用的堆内容则会抛出OutOfMemoryError

什么是内存泄露

内存泄露表示当前对象被大于其本身生命周期的其他对象持有引用,而GC只会去回收堆内存中没有被引用的的对象,这样导致内存中持有这些实例不能被回收,然后慢慢积累越来越多造成卡顿甚至内存溢出

MAT下载

MAT有对应的eclipse插件,但是大部分开发是在studio下进行的,因此附上MAT应用的下载地址
MAT的下载地址

创建内容泄露实例

创建主Activity
public class MainMATActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mat_main);
        findViewById(R.id.btn_go).setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainMATActivity.this, LeakMemoryActivity.class);
                startActivity(intent);
        }
});        
创建单例模式的LeakManager,在构造函数里引用Activity的Context
public class LeakManager {
    private static final String TAG=LeakManager.class.getSimpleName();
    public Context context;
    private static LeakManager leakEntity=null;

    public static LeakManager getInstance(Context context) {
        if (leakEntity == null) {
            leakEntity = new LeakManager(context);
        }
        return leakEntity;
    }

    private LeakManager(Context context) {
        this.context = context;
    }
    public void doTask(){
        Log.e(TAG,"哈哈,吃的别想让我吐出来!");
    }
}
创建内存泄露Activity
public class LeakMemoryActivity extends Activity {
    private static final String TAG = LeakMemoryActivity.class.getSimpleName();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView textView = new TextView(this);
        textView.setGravity(Gravity.CENTER);
        textView.setText("Leark Memory Activity");
        setContentView(textView);
        LeakManager.getInstance(this).doTask();

    }
}
在第三步中我们创建了LeakMemoryActivity并完成LeakManager单例模式下的初始化,也就是说LeakManger已经保持了LeakMemory的Context引用.接下来启动APP.

生成hprof分析文件

打开Android Device Monitor、选中需要监听的应用程序、点击update heap



这时候右边显示一个Heap界面如下,如果没有点击Window->Show View->Android->Heap,然后点击Cause GC即可出发垃圾回收。HeapSize表示APP堆内存大小,Allocated表示已经分配的内存大小

点击MainMATActivity按钮跳转到LeakMemoryActivity,然后返回然后在跳转、返回,随后点击Dump HPROF file生成hprof文件,如果项目较大可能会慢一些,然后保存到指定位置

这个时候生成的一般是包名.hprof文件,这个文件目前还不能被MAT打开,需要SDK的命令转换一下
hprof-conv /Users/.../com.ymy.app.hprof(源文件路径) /Desktop/hprof/out.hprof(转换后的路径)

使用MAT分析内存使用情况

打开MAT

选择cancle,点击 Open a Heap Dump,打开刚才转换后的out.hprof文件

之后展示的页面,下载的Actions中有Histogram(列出内存中对象的名称数量大小)、Dominator Tree(将内存中的对象按大小进行排序)

先介绍下几个关键词表示的意思
Objects

当前对象的数量

Shallow Heap

表示当前对象抛开引用关系自己所占的内存大小

Retained Heap

表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,在Histogram下没有具体值

Histogram分析

上面显示了内存中每个对象的名称、数量、大小等,是不是看着有点懵,别怕,在它的第一行支持正则还有模糊搜索,我在这里输入包名的关键字‘ymy’,然后回车

这里我们就熟悉了吧哈,也许有人会好奇MainMATActivity$1什么意思,我们不是在MainMATActivity里有个setOnClickListener事件用于跳转到LeakMemoryActivity么,OnClickListener是一个内部类,$1代表MainMATActivity的第一个内部类,我们主要是分析LeakMemoryActivity内存泄露问题,因此右键选中它,选择listObjects,这里有两个选项with outgoing references代表当前对象引用的外部对象,with incoming references表示引用了当前对象的外部对象。

根据上面的代码我们知道LeakManager保持了LeakMemoryActivity context的引用,导致无法被正常回收,因此我们选择listobjects–>with incoming refrence验证一下

通过这个我们可以看到LeakMemoryActivity的context被LeakManger的单例对象leakEntity引用
Dominator Tree分析
同样支持正则、模糊搜索

仔细观察我们可以看到对象名最左边的图标有一个桔黄色的圆圈,这是代表什么意思呢这里要简单说一下GC的回收机制,GC操作会从一个叫作Roots的对象开始检查,所有它可以访问到的对象表示在使用中即被保持着引用,不去回收,访问不到的回收掉,这个圆圈就表示当前的对象可以被GC Roots访问,无法被回收,但是并不一定代表是内存泄露,可以列为怀疑对象通过Listobjects、path to GC ROOTS去分析

TIPS

我们也可以通过QQL执行查询当前还在内存中的activity