分析

打开MAT工具,选择file-Open Heap Dump,打开转换后的hprof文件,选择Leak Suspects Report选项,如图6.67所示。

分析 - 图1 图6.67 开启MAT

MAT提供的数据非常多,但有两个功能是很常用的,就是它的Histogram和DominatorTree功能。Histogram可以统计内存中对象的名称、种类、实例数和大小,而DominatorTree则是建立这些内存对象之间的联系。整个界面如图6.68所示。

分析 - 图2 图6.68 MAT分析

这里汇总了分析报告,点击可以查看报告详情。

Histogram

点击工具栏中的Histogram按钮即可打开该界面,如图6.69所示。

在列表顶部的class name区域,可以输入过滤条件,通常Activity的内存泄漏,可以直接通过输入Activity名来获取与之相关的实例,如图6.70所示。

分析 - 图3 图6.69 Histogram

分析 - 图4 图6.70 过滤条件

代码中创建的MainActivity和LeakThread创建了17次之多,基本可以断定是内存泄漏了。那么具体是如何泄漏的呢?可以通过查看GC对象的引用链来进行分析。在MainActivity上单击鼠标右键,选择Merge Shortest paths to GC Roots,并通过弹出的列表选择相关类型的引用(强、软、弱、虚),分析不同引用类型下的GC情况,这里选择With all references,如图6.71所示。

分析 - 图5 图6.71 选择reference类型

这样系统就对GC对象进行了分析,并找到了GC引用链,如图6.72所示。

分析 - 图6 图6.72 查找GC引用链

到这里整个内存泄漏点就一目了然了,MainActivity的实例被内部类LeakThread所引用,形成一条GC引用链,导致无法被GC。

同时,在Histogram中还可以查看一个对象到底包含了哪些对象的引用。例如,要查看MainActivity所包含的引用,直接单击鼠标右键,选择list objects-with incoming references(显示选中对象被哪些外部对象引用,而outcoming是指选中对象持有哪些对象的引用),如图6.73所示。

分析 - 图7 图6.73 查找引用

这样在弹出的列表中就可以获取该Activity所引用的对象实例,如图6.74所示。

分析 - 图8 图6.74 查看引用实例

DominatorTree

DominatorTree与Histogram的使用方法类似,但是DominatorTree更侧重于关系的分析,而Histogram更侧重于量的分析。

打开DominatorTree,同样在列表第一行进行过滤MainActivity,如图6.75所示。

分析 - 图9 图6.75 DominatorTree

可以发现这里包含了大量的MainActivity实例,通过这个数量基本就可以发现存在内存泄漏了。但是DominatorTree的功能远不止这么简单。在了解它的其他功能之前,还需要了解它的一些符号含义。

  • ShallowHeap:对象自身占用的内存大小,不包括它引用的对象。
  • RetainedHeap:对象自身占用的内存大小,加上它直接或者间接引用的对象大小。

简单地说,就是个人与团体的区别。通常情况下,开发者通过RetainedHeap来调查对象所占内存大小,RetainedHeap内存占用大的对象,应该被列为重点怀疑对象。

  • 文件图标:每个文件图标都代表一个对象,并占据图表中的一行。

但仔细看,实际上这些文件图标是不一样的。有些文件右下角会有一个小圆点,如果有这个小圆点,则代表该对象可以被GC系统访问到。也就是说,带小圆点的对象都有可能是内存泄漏的可疑对象。当然,其中大部分都是持有正常引用的对象。

  • 对象类型:在某些对象的数据行最后,可能会出现一个标识,例如“System Class”、“Thread”等,这些标识代表对象的类型。如果该对象是System Class,那么就可以直接略过了(但不排除它的其他引用可能泄漏)。

在了解了这些标识之后,就可以着手进行分析了。

这里的例子比较简单,首先与Histogram一样,先过滤出相关的Activity,即MainActivity,如图6.76所示。

分析 - 图10 图6.76 过滤条件

可以发现,MainActivity并没有被GC Root访问到(文件图标上没有小圆点),那么是不是代表它会被回收呢?当然不是,程序员的嗅觉应该让你发现,如果真能回收,怎么会出现这么多实例!OK,那么怎么找到它的GC Root呢?找到任意一个MainActivity的实例,单击鼠标右键,选择“Path To GC Roots”-“With all references”,这一点与Histogram也非常类似,如图6.77所示。

分析 - 图11 图6.77 Path To GC Roots

这里开发者同样可以对各种引用类型(强、软、弱、虚)进行排除,从而找到合适的GC Root,结果如图6.78所示。

分析 - 图12 图6.78 引用结果

可以发现,虽然MainActivity没有被GC Root访问到,但是它却持有了一个this$0对象。这个对象实际上就是Java中的内部类,数据行里面也清楚显示了,它就是LeakThread对象。正是这个对象被GC Root访问到了,才导致持有它的MainActivity无法被回收!

这里的例子比较简单,通常情况下,开发者首先需要根据RetainedHeap大小来确定嫌疑犯,再通过Path To GC Roots找到GC引用链调查它是否真的有嫌疑。

QQL

除了上面两种常见的分析方式,MAT还提供了一种类似数据库查询的方式来进行数据分析。毕竟MAT产生的数据太多了,如果没有一个高效的查询方式,则很难发挥它的强大作用。点击菜单栏中的QQL按钮,打开QQL界面,如图6.79所示。

分析 - 图13 图6.79 功能标签

显示界面,如图6.80所示。

分析 - 图14 图6.80 QQL界面

这里MAT提供了一个类似SQL查询的语法,帮助开发者搜索相关信息数据。例如前面的例子,通过以下指令,可以查询Activity的实例数,如图6.81所示。

  1. select * from instanceof android.app.Activity

分析 - 图15 图6.81 QQL查询

通过这种方式,开发者同样可以找到内存泄漏点。