【Android -- 性能优化】内存分析工具 — MAT

不断学习,做更好的自己!💪

视频号CSDN简书
欢迎打开微信,关注我的视频号:程序员朵朵点我点我

简介

MAT 是 Memory Analyzer tool 的缩写,是一种快速,功能丰富的Java 堆分析工具,能帮助你查找内存泄漏和减少内存消耗。很多情况下,我们需要处理测试提供的 hprof 文件,分析内存相关问题,那么 MAT 也绝对是不二之选。Eclipse 可以下载插件结合使用,也可以作为一个独立分析工具使用,下载地址:MAT

获取内存快照并分析

1. 获取内存快照
为了便于大家理解,我们先编写一个用于调试的单例MemorySingleton,它内部包含一个成员变量ObjectA,而ObjectA又包含了ObjectB和ObjectC,以及一个长度为4096的int数组,ObjectB和ObjectC各自包含了一个ObjectD,ObjectD中包含了一个长度为4096的int数组,在Activity的onCreate()中,我们初始化这个单例对象。

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 一栏中,可以看到应用占用的内存数值,它的界面分为几个部分:
01.png

我们先点击2主动触发一次垃圾回收,再点击3来获得内存快照,等待一段时间后,会在窗口的左上部分获得后缀为hprof的分析报表:
02.png

在生成的 hprof 上点击右键,就可以导出为标准的 hprof 用于 MAT分析,在屏幕的右边,Android Studio 也提供了分析的界面,今天我们先不介绍它,而是导出成MAT可识别的分析报表。
03.png

2. 使用MAT分析报表
运行 MAT,打开我们导出的标准 hprof 文件:
04.png

经过一段时间的转换之后,会得到下面的Overview界面,我们主要关注的是Actions部分:
05.png

Actions包含了四个部分,点击它可以得到不同的视图:

  • Histogram:
  • Dominator Tree
  • Top Consumers
  • Duplicate Classes

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

Histogram
点开 Histogram 之后,会得到以下的界面:
06.png

这个视图中提供了多种方式来对对象进行分类,这里为了分析方便,我们选择按包名进行分类:
07.png

要理解这个视图,最关键的是要明白红色矩形框中各列的含义,下面,我们就以在 MemorySingleton 中定义的成员对象为例,来一起理解一下它们的含义:
08.png

  • Objects:表示该类在内存当中的对象个数。
    这一列比较好理解,ObjectA包含了ObjectB和ObjectC两个成员变量,而它们又各自包含了ObjectD,因此内存当中有2个ObjectD对象。
  • Shallow Heap
    这一列中文翻译过来是“浅堆”,表示的是对象自身所占用的内存大小,不包括它所引用的对象的内存大小。举例来说,ObjectA包含了int[]、ObjectB和ObjectC这三个引用,但是它并不包括这三个引用所指向的int[]数组、ObjectB对象和ObjectC对象,它的大小为24个字节,ObjectB/C/D也是同理。
  • Retained Heap
    这一列中文翻译过来是“保留堆”,也就是当该对象被垃圾回收器回收之后,会释放的内存大小。举例来说,如果ObjectA被回收了之后,那么ObjectB和ObjectC也就没有对象继续引用它了,因此它也被回收,它们各自内部的ObjectD也会被回收,如下图所示:
    09.png

因为 ObjectA 被回收之后,它内部的 int[] 数组,以及 ObjectB/ObjectC 所包含的 ObjectD 的 int[] 数组所占用的内存都会被回收,也就是:

retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC

下面,我们考虑一种比较复杂的情况,我们的引用情况变为了下面这样:
10.png

对应的代码为:

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];
}

我们重新抓取一次内存快照,那么情况就变为了:
11.png

可以看到 ObjectE 的 Retained Heap 大小仅仅为 16 字节,和它的Shallow Heap 相同,这是因为它内部的成员变量 objectF 所引用的 ObjectF,也同时被 MemorySingleton 中的成员变量 objectF 所引用,因此 ObjectE 的释放并不会导致 objectF 对象被回收。
总结一下,Histogram 是从类的角度来观察整个内存区域的,它会列出在内存当中,每个类的实例个数和内存占用情况。

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

  • List Objects:
    12.png

  • incomming reference表示它被那些对象所引用
    13.png

  • outgoing则表示它所引用的对象

  • Show objects by class
    和上面的选项类似,只不过列出的是类名。

  • Merge Shortest Paths to GC Roots,我们可以选择排除一些类型的引用:
    14.png

到Gc根节点的最短路径,以ObjectD为例,它的两个实例对象到Gc Roots的路径如下,这个选项很重要,当需要定位内存泄漏问题的时候,我们一般都是通过这个工具:
15.png

dominator_tree
dominator_tree则是通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的。例如下图,只有MemorySingleton的Retain Heap的大小被计算出来,而它内部的成员变量的Retain Heap都为0:
16.png

要获得更详细的情况,我们需要通过它的outgoing,也就是它所引用的对象来分析:
17.png

可以看到它的outgoing视图中有两个objectF,但是它们都是指向同一片内存空间@0x12d8d7f0,通过这个视图,我们可以列出那么占用内存较多的对象,然后一步步地分析,看究竟是什么导致了它所占用如此多的内存,以此达到优化性能的目的。

分析 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所引用,因此它并不能被回收,此时的内存快照为:
18.png

我们通过查看它到Gc Roots的引用链,就可以分析出它为什么没有被回收了:
19.png

小结

通过 Android Studio 和 MAT 结合,我们就可以获得某一时刻内存的使用情况,这样我们很好地定位内存问题,是每个开发者必须要掌握的工具!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kevin-Dev

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值