文章参考:https://www.javatang.com/archives/2017/11/08/11582145.html

文章参考:http://vivianking6855.github.io/2018/05/04/Android-optimization-AS-MAT/

文章参考:https://blog.csdn.net/Jin_Kwok/article/details/80326088

概述

MAT是Eclipse出品的一个插件,当然也有独立的版本。下载链接:https://www.eclipse.org/mat/downloads.php

MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要根据自己的实际代码和业务逻辑去分析这些数据,判断到底是不是真的发生了内存泄漏。

MAT支持对标准格式的hprof文件进行内存分析,所以,我们要先在Android Studio里先把Java heap文件转成标准格式的hprof文件,具体步骤如下:

​ 1、点击左侧的capture,选择对应的文件,并右键选择“Export to standard .hprof”导出标准的hprof文件:

MAT的使用

当成功启动MAT后,通过菜单选项“File->Open heap dump…”打开指定的dump文件后,将会生成Overview选项,如下所示:

MAT提供了很多功能,但是最常用的只有Histogram(直方图)和Dominator Tree(支配树),通过Histogram可以直观地看出内存中不同类型的buffer的数量和占用的内存大小,而Dominator Tree则把内存中的对象按照从大到小的顺序进行排序,并且可以分析对象之间的引用关系,内存泄露分析就是通过Dominator Tree来完成的。

我们先来看一下这两种图的样式:

Histogram(直方图)

下面是Histogram(直方图)

Dominator Tree(支配树)

下面的是Dominator Tree(支配树)

我们可以简单的介绍一下这两张图的一些属性。

1、Shallow Heap 表示对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的内存总和。

2、Retained Heap(保留堆空间) 表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,换句话说,Retained Heap是该对象GC之后所能回收到内存的总和。

从上图中看,前两行的Retained Heap是最大的,分析内存泄漏时,内存最大的对象也是最应该去怀疑的。

3、Percentage(百分比)

表示这个对象所占用的内存占总内存的百分比。同样我们在排查内存问题的的时候。要找这些占用内存百分比最高的对象类进行重点分析。但是根据经验来看,其实占用内存百分比最高的前几位基本都是一些BitMap之类的。

4、Objects(对象数目)

标志这个对象的在内存中的数目,就像我的柱状图的截图中一样。我们一般在进行柱状图分析的时候。我们一般按照我们的包名来进行过滤业务代码。然后根据业务代码里面各个对象的个数。从而过滤出个数比较多的对象数。这些一般可能就是内存泄漏嫌疑比较大的对象。

下面,我们再来分析一下Dominator Tree(支配树)上面的一些属性。

我们可以看到在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,可以被GC Root访问到的对象都是无法被回收的。
那么这就可以说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。

另外,我们还发现有的对象右边有写着System Class,那么说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。这些都可以辅助我们尽快的缩减排查的对象。快速定位内存泄漏的对象。

MAT排查内存泄漏步骤

将堆转储另存为 HPROF 文件

捕获堆转储后,只有在内存性能分析器正在运行时,才能在该分析器中查看数据。当您退出分析会话时,会丢失堆转储。因此,如果您要保存堆转储以供日后查看,请将其导出到 HPROF 文件。

在 Android Studio 3.1 及更低版本中,Export capture to file 按钮 img 位于时间轴下方工具栏的左侧;在 Android Studio 3.2 及更高版本中,Sessions 窗格中每个 Heap Dump 条目的右侧都有一个 Export Heap Dump 按钮。在随即显示的 Export As 对话框中,使用 .hprof 文件扩展名保存文件。

您可以使用 android_sdk/platform-tools/ 目录中提供的 hprof-conv 工具执行此操作。运行包含两个参数(即原始 HPROF 文件和转换后 HPROF 文件的写入位置)的 hprof-conv 命令。例如:

1
hprof-conv heap-original.hprof heap-converted.hprof

确认我们分析内存泄漏的粒度

分析内存泄露时,必须要掌握粒度,所谓粒度就是你此刻dump的hprof文件究竟是分析谁的泄露,如果你在开始前心中没有个目标,最后取出来的hprof也分析不出什么原因。粒度越小,对你分析问题也就越有利,当你把一个个小粒度问题解决后,整个App的泄露就迎刃而解了。

比如,假如现在有个项目包含Module几十个,每个Module包含的Activity数以百计,现在让你分析它是否内存泄露,如果你只是胡乱抓个hprof根本分析不出什么。假如你就针对某个Activity分析这样问题就简单多了。比如你现在分析MomeryLeakActivity的内存泄露问题,你可以参考如下步骤:

Step1:进入MomeryLeakActivity之前,你先dump个hprof文件HprofA;

Step2:进入MomeryLeakActivity操作一会,再退出MomeryLeakActivity后dump个hprof文件HprofB;

Step3:采用Histogram和Dominator Tree对比分析这两个Hprof文件,即可得出MomeryLeakActivityA是否泄露

那么怎么对比两个Hprof文件呢?

打开我们需要分析对比的两个快照文件,然后切到柱状图模式下,然后选择右上角的对比按钮。在Select Baseline的弹窗中,选择要对比的快照。

image

我们看到有大量的跟我们业务相关的View的泄漏。还包括我们MomeryLeakActivity的Activity和其内部的View基本都泄露了两份。
所以我们现在基本就算是找到泄漏点了。

按照对象从到小排列起来,逐个分析

为了分析内存泄露,我们需要分析Dominator Tree(支配树)里面的内存信息,在Dominator Tree中内存泄露的原因一般不会直接显示出来,这个时候需要按照从大到小的顺序去排查一遍。

当然。我们可以再柱状图里面通过我们程序的包名来进行排查。看我们的包名中的类的对象实现,哪些分配的数量过多。基本可以看到猜测哪些对象可能存在内存泄漏。

GC Root树的遍历查询。

经过上面的排序,我们大概能看出哪些对象发生了泄漏(发生泄漏的对象,部分对象在内存中残留的数量比较多)。

image

下面我们就是看这些对象到底是在哪里泄漏的

首先,我们选中它然后单击鼠标右键->Path To GC Roots->exclude wake/soft references

当然,我们也可以点击鼠标右键->Merge Shortest Path to GC Roots ->exclude wake/soft references

然后找到他的GC Root。 并在代码中查清GC Root没有被释放的原因。这样我们就找个这个对象内存泄漏的原因

Path To GC Roots过程中之所以选择排除弱引用和软引用,是因为二者都有较大几率被gc回收掉,它们并不能造成内存泄露。

image

从上图中,我们可以看到MomeryLeakActivity对象被一个匿名线程类所引用着。

Dominator Tree(支配树) 的搜索功能 。

Dominator Tree界面中是可以使用搜索功能的,比如我们尝试搜索Bitmap.

搜索后的结果如图所示。

MAT其他功能

左边的inspector可以查看对象内部的各种信息。

其它重要选项

1. List objects :

  • with incoming references 引用到该对象的对象
  • with outcoming references 被该对象引用的对象

2. Show objects by class :

  • incoming references 引用到该对象的对象
  • outcoming references 被该对象引用的对象

优化建议

1、使用线程时,一定要确保线程在周期性对象(如Activity)销毁时能正常结束,如能正常结束,但是Activity销毁后还需执行一段时间,也可能造成泄露,此时可采用WeakReference方法来解决,另外在使用Handler的时候,如存在Delay操作,也可以采用WeakReference;

2、使用Handler + HandlerThread时,记住在周期性对象销毁时调用looper.quit()方法;

3、建议少使用匿名类或内部类,可考虑使用嵌套类(带static那种类),减少对周期性对象的隐性持有;