使用MAT排查Android内存泄漏

前言:

MAT是Eclipse出品的一个插件,当然也有独立的版本。下载地址:点我

在这里先提醒一下:
1、本篇文章假定你已经可以拿到hprof文件,如果不懂这个文件,请自行查询
2、MAT并不会准确地告诉我们哪里发生了内存泄漏,而是会提供一大堆的数据和线索,我们需要根据自己的实际代码和业务逻辑去分析这些数据,判断到底是不是真的发生了内存泄漏。

hprof文件格式转换

MAT仅支持对标准格式的hprof文件进行内存分析,所以,我们首先要使用AndroidSDK的platform-tools目录下提供的hprof-conv.exe工具,先把Java heap文件转成标准格式的hprof文件,步骤如下:
1、首先要获取hprof文件,我们这里假设名字叫做01.hprof
2、配置hprof-conv.exe工具类的环境变量
3、打开命令行窗口,进入到01.hprof文件所在的目录
4、输入命令行进行文件格式转换:hprof-conv -z 01.hprof 02.hprof
5、成功得到02.hprof文件

初步使用MAT工具导入hprof文件

点击左上角菜单栏中的file->open file 选择02.hprof,操作成功之后,会出现一个类似下图的页面:
01

开始使用MAT工具导入hprof文件

MAT中提供了非常多的功能,这里我们只要学习几个最常用的就可以了。上图那个饼状图展示了最大的几个对象所占内存的比例,这张图中提供的内容并不多,我们可以忽略它。在这个饼状图的下方就有几个非常有用的工具了。

Histogram:

直方图,可以列出内存中每个对象的名字、数量以及大小。
00

Dominator Tree:

会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。

1)Dominator Tree
02
从上图可以看到右边存在着3个参数。Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,因此从上图中看,前两行的Retained Heap是最大的,分析内存泄漏时,内存最大的对象也是最应该去怀疑的。

另外大家应该可以注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,
可以被GC Root访问到的对象都是无法被回收的。那么这就可以说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。
如果发现有的对象右边有写着System Class,那么说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。

根据我们在Android studio的Java heap文件的提示,TestActivity对象有可能发生了内存泄漏,于是我们直接在上面搜TestActivity(这个搜索功能也是很强大的):

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

当然,如果你觉得按照默认的排序方式来查看不方便,你可以自行设置排序的方式:

  • Group by class
  • Group by class loader
  • Group by package
    04
    从上图可以看出,我们搜出了3个TestActivity的对象,一般在退出某个activity后,就结束了一个activity的生命周期,应该会被GC正常回收才对的。通常情况下,一个activity应该只有1个实例对象,但是现在居然有3个TestActivity对象存在,说明之前的操作,产生了3个TestActivity对象,并且无法被系统回收掉。
    接下来继续查看引用路径。

对着TestActivity对象点击右键 -> Merge Shortest Paths to GC Roots(当然,这里也可以选择Path To GC Roots) -> exclude all phantom/weak/soft etc. references

为什么选择exclude all phantom/weak/soft etc. references呢?因为弱引用等是不会阻止对象被垃圾回收器回收的,所以我们这里直接把它排除掉

05
接下来就能看到引用路径关系图了:
06
从上图可以看出,TestActivity是被this$0所引用的,它实际上是匿名类对当前类的引用。this$0又被callback所引用,接着它又被Message中一串的next所引用…到这里,我们就已经分析出内存泄漏的原因了,接下来就是去改善存在问题的代码了。

2)Histogram
07
这里是把当前应用程序中所有的对象的名字、数量和大小全部都列出来了,那么Shallow Heap又是什么意思呢?就是当前对象自己所占内存的大小,不包含引用关系的。

上图当中,byte[]对象的Shallow Heap最高,说明我们应用程序中用了很多byte[]类型的数据,比如说图片。可以通过右键 -> List objects -> with incoming references来查看具体是谁在使用这些byte[]。

当然,除了一般的对象,我们还可以专门查看线程对象的信息:
08
Histogram中是可以显示对象的数量的,比如说我们现在怀疑TestActivity中有可能存在内存泄漏,就可以在第一行的正则表达式框中搜索“TestActivity”,如下所示:
09

接下来对着TestActivity右键 -> List objects -> with outgoing references查看具体TestActivity实例

注:
List objects -> with outgoing references :表示该对象的出节点(被该对象引用的对象)
List objects -> with incoming references:表示该对象的入节点(引用到该对象的对象)

如果想要查看内存泄漏的具体原因,可以对着任意一个TestActivity的实例右键 -> Merge Shortest Paths to GC Roots(当然,这里也可以选择Path To GC Roots) -> exclude all phantom/weak/soft etc. references,如下图所示:
10
11
从这里可以看出,Histogram和Dominator Tree两种方式下操作都是差不多的,只是两种统计图展示的侧重点不太一样,实际操作中,根据需求选择不同的方式即可。

3)两个hprof文件的对比
为了排查内存泄漏,经常会需要做一些前后的对比。下面简单说一下两种对比方式:

1.直接对比

工具栏最右边有个“Compare to another heap dump”的按钮,只要点击,就可以生成对比后的结果。(注意,要先在MAT中打开要对比的hprof文件,才能选择对比的文件):
12
2.添加到campare basket里对比

在window菜单下面选择compare basket:
13
在文件的Histogram view模式下,在navigation history下选择add to compare basket:
14
然后就可以通过 Compare Tables 来进行对比了:
15

总结

最后,还是要再次提醒一下,工具是死的,人是活的,MAT也没有办法保证一定可以将内存泄漏的原因找出来,还是需要我们对程序的代码有足够多的了解,知道有哪些对象是存活的,以及它们存活的原因,然后再结合MAT给出的数据来进行具体的分析,这样才有可能把一些隐藏得很深的问题原因给找出来。

Guess you like

Origin blog.csdn.net/abc6368765/article/details/106770665