性能优化工具知识梳理(5) MAT

1、概述

内存一直都是性能优化的重点,今天咱们主要介绍如何使用Android Studio生成分析hprof报表,并使用MAT分析结果,在介绍以前,首先须要感谢Gracker,本文的分析大多数都是来自于它的这篇文章:数组

http://www.jianshu.com/p/d8e247b1e7b2性能优化

2、获取内存快照并分析

2.1 获取内存快照

为了便于你们理解,咱们先编写一个用于调试的单例MemorySingleton,它内部包含一个成员变量ObjectA,而ObjectA又包含了ObjectBObjectC,以及一个长度为4096int数组,ObjectBObjectC各自包含了一个ObjectDObjectD中包含了一个长度为4096int数组,在ActivityonCreate()中,咱们初始化这个单例对象。bash

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }

    private MemorySingleton() {
        objectA = new ObjectA();
    }

}

public class ObjectA {
    private int[] dataOfA = new int[4096];
    private ObjectB objectB = new ObjectB();
    private ObjectC objectC = new ObjectC();
}

public class ObjectB {
    private ObjectD objectD = new ObjectD();
}

public class ObjectC {
    private ObjectD objectD = new ObjectD();
}

public class ObjectD {
    private int[] dataOfD = new int[4096];
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance();
    }
}
复制代码

Android Studio最下方的Monitors/Memory一栏中,能够看到应用占用的内存数值,它的界面分为几个部分:dom

咱们先点击 2主动触发一次垃圾回收,再点击 3来得到内存快照,等待一段时间后,会在窗口的左上部分得到后缀为 hprof的分析报表:
在生成的 hprof上点击右键,就能够导出为标准的 hprof用于 MAT分析,在屏幕的右边, Android Studio也提供了分析的界面,今天咱们先不介绍它,而是导出成 MAT可识别的分析报表。

2.2 使用MAT分析报表

运行MAT,打开咱们导出的标准hprof文件: ide

通过一段时间的转换以后,会获得下面的 Overview界面,咱们主要关注的是 Actions部分:
Actions包含了四个部分,点击它能够获得不一样的视图:

  • Histogram
  • Dominator Tree
  • Top Consumers
  • Duplicate Classes

在平时的分析当中,主要用到前两个视图,下面咱们就来依次看一下怎么使用这两个视图来进行分析内存的使用状况。工具

2.2.1 Histogram

点开Histogram以后,会获得如下的界面: 性能

这个视图中提供了多种方式来对对象进行分类,这里为了分析方便,咱们选择按包名进行分类:
要理解这个视图,最关键的是要明白红色矩形框中各列的含义,下面,咱们就以在 MemorySingleton中定义的成员对象为例,来一块儿理解一下它们的含义:

  • Objects:表示该类在内存当中的对象个数。 这一列比较好理解,ObjectA包含了ObjectBObjectC两个成员变量,而它们又各自包含了ObjectD,所以内存当中有2ObjectD对象。
  • Shallow Heap 这一列中文翻译过来是“浅堆”,表示的是对象自身所占用的内存大小,不包括它所引用的对象的内存大小。举例来讲,ObjectA包含了int[]ObjectBObjectC这三个引用,可是它并不包括这三个引用所指向的int[]数组、ObjectB对象和ObjectC对象,它的大小为24个字节,ObjectB/C/D也是同理。
  • Retained Heap 这一列中文翻译过来是“保留堆”,也就是当该对象被垃圾回收器回收以后,会释放的内存大小。举例来讲,若是ObjectA被回收了以后,那么ObjectBObjectC也就没有对象继续引用它了,所以它也被回收,它们各自内部的ObjectD也会被回收,以下图所示:
    由于ObjectA被回收以后,它内部的int[]数组,以及ObjectB/ObjectC所包含的ObjectDint[]数组所占用的内存都会被回收,也就是:
retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC
复制代码

下面,咱们考虑一种比较复杂的状况,咱们的引用状况变为了下面这样: 优化

对应的代码为:

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    private ObjectE objectE;
    private ObjectF objectF;
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }
    private MemorySingleton() {
        objectA = new ObjectA();
        objectE = new ObjectE();
        objectF = new ObjectF();
        objectE.setObjectF(objectF);
    }
}

public class ObjectE {
    private ObjectF objectF;
    public void setObjectF(ObjectF objectF) {
        this.objectF = objectF;
    }
}

public class ObjectF {
    private int[] dataInF = new int[4096];
}
复制代码

咱们从新抓取一次内存快照,那么状况就变为了: ui

能够看到 ObjectERetained Heap大小仅仅为 16字节,和它的 Shallow Heap相同,这是由于它内部的成员变量 objectF所引用的 ObjectF,也同时被 MemorySingleton中的成员变量 objectF所引用,所以 ObjectE的释放并不会致使 objectF对象被回收。

总结一下,Histogram是从类的角度来观察整个内存区域的,它会列出在内存当中,每一个类的实例个数和内存占用状况。this

分析完这三个比较重要的列含义以后,咱们再来看一下经过右键点击某个Item以后的弹出列表中的选项:

  • List Objects
  • incomming reference表示它被那些对象所引用
  • outgoing则表示它所引用的对象
  • Show objects by class 和上面的选项相似,只不过列出的是类名。
  • Merge Shortest Paths to GC Roots,咱们能够选择排除一些类型的引用:
    Gc根节点的最短路径,以ObjectD为例,它的两个实例对象到Gc Roots的路径以下,这个选项很重要,当须要定位内存泄漏问题的时候,咱们通常都是经过这个工具:

2.2.2 dominator_tree

dominator_tree则是经过“引用树”的方式来展示内存的使用状况的,通俗点来讲,它是站在对象的角度来观察内存的使用状况的。例以下图,只有MemorySingletonRetain Heap的大小被计算出来,而它内部的成员变量的Retain Heap都为0

要得到更详细的状况,咱们须要经过它的 outgoing,也就是它所引用的对象来分析:
能够看到它的 outgoing视图中有两个 objectF,可是它们都是指向同一片内存空间 @0x12d8d7f0,经过这个视图,咱们能够列出那么占用内存较多的对象,而后一步步地分析,看到底是什么致使了它所占用如此多的内存,以此达到优化性能的目的。

2.3 分析Activity内存泄漏问题

在平时的开发中,咱们最容易遇到的就是Activity内存泄漏,下面,咱们模拟一下这种状况,并演示一下经过MAT来分析内存泄漏的缘由,首先,咱们编写一段会致使内存泄漏的代码:

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private Context mContext;
    public static synchronized MemorySingleton getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new MemorySingleton(context);
        }
        return sInstance;
    }
    private MemorySingleton(Context context) {
        mContext = context;
    }
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance(this);
    }
}
复制代码

咱们启动MemoryActivity以后,而后按back退出,按照常理来讲,此时它应当被回收,可是因为它被MemorySingleton中的mContext所引用,所以它并不能被回收,此时的内存快照为:

咱们经过查看它到 Gc Roots的引用链,就能够分析出它为何没有被回收了:

3、小结

经过Android StudioMAT结合,咱们就能够得到某一时刻内存的使用状况,这样咱们很好地定位内存问题,是每一个开发者必需要掌握的工具!


更多文章,欢迎访问个人 Android 知识梳理系列:

相关文章
相关标签/搜索