那么通过下面学习的 GC 日志以及 DDMS 工具这两种形式,当初咱们曾经能够比拟轻松地发现应用程序中是否存在内存泄露的景象了。然而如果真的呈现了内存泄露,咱们应该怎么定位到具体是哪里出的问题呢?这就须要借助一个内存剖析工具了,叫做 Eclipse Memory Analyzer(MAT)。咱们须要先将这个工具下载下来,下载地址是:http://eclipse.org/mat/downlo…。这个工具分为 Eclipse 插件版和独立版两种,如果你是应用 Eclipse 开发的,那么能够应用插件版 MAT,十分不便。如果你是应用 Android Studio 开发的,那么就只能应用独立版的 MAT 了。
下载好了之后上面咱们开始学习如何去剖析内存泄露的起因,首先还是进入到 DDMS 界面,而后在左侧面板选中咱们要察看的应用程序过程,接着点击 Dump HPROF file 按钮,如下图所示:
点击这个按钮之后须要期待一段时间,而后会生成一个 HPROF 文件,这个文件记录着咱们应用程序外部的所有数据。然而目前 MAT 还是无奈关上这个文件的,咱们还须要将这个 HPROF 文件从 Dalvik 格局转换成 J2SE 格局,应用 hprof-conv 命令就能够实现转换工作,如下所示:
hprof-conv dump.hprof converted-dump.hprof
hprof-conv 命令文件寄存于 <Android Sdk>/platform-tools 目录上面。另外如果你是应用的插件版的 MAT,也能够间接在 Eclipse 中关上生成的 HPROF 文件,不必通过格局转换这一步。
好的,接下来咱们就能够来尝试应用 MAT 工具去剖析内存透露的起因了,这里须要揭示大家的是,MAT 并不会精确地通知咱们哪里产生了内存透露,而是会提供一大堆的数据和线索,咱们须要本人去剖析这些数据来去判断到底是不是真的产生了内存透露。那么当初运行 MAT 工具,而后抉择关上转换过后的 converted-dump.hprof 文件,如下图所示:
MAT 中提供了十分多的性能,这里咱们只有学习几个最罕用的就能够了。上图最地方的那个饼状图展现了最大的几个对象所占内存的比例,这张图中提供的内容并不多,咱们能够疏忽它。在这个饼状图的下方就有几个十分有用的工具了,咱们来学习一下。
Histogram 能够列出内存中每个对象的名字、数量以及大小。
Dominator Tree 会将所有内存中的对象按大小进行排序,并且咱们能够剖析对象之间的援用构造。
个别最罕用的就是以上两个性能了,那么咱们先从 Dominator Tree 开始学起。
当初点击 Dominator Tree,后果如下图所示:
这张图蕴含的信息十分多,我来带着大家一起解析一下。首先 Retained Heap 示意这个对象以及它所持有的其它援用(包含间接和间接)所占的总内存,因而从上图中看,前两行的 Retained Heap 是最大的,咱们剖析内存透露时,内存最大的对象也是最应该去狐疑的。
另外大家应该能够留神到,在每一行的最右边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就示意是能够被 GC Roots 拜访到的,依据下面的解说,能够被 GC Root 拜访到的对象都是无奈被回收的。那么这就阐明所有带红色的对象都是透露的对象吗?当然不是,因为有些对象零碎须要始终应用,原本就不应该被回收。咱们能够留神到,上图当中所有带红点的对象最左边都有写一个 System Class,阐明这是一个由系统管理的对象,并不是由咱们本人创立并导致内存透露的对象。
那么上图中就无奈看出内存透露的起因了吗?的确,内存透露原本就不是这么容易找出的,咱们还须要进一步进行剖析。上图当中,除了带有 System Class 的行之外,最大的就是第二行的 Bitmap 对象了,尽管 Bitmap 对象当初不能被 GC Roots 拜访到,但不代表着 Bitmap 所持有的其它援用也不会被 GC Roots 拜访到。当初咱们能够对着第二行点击右键 -> Path to GC Roots -> exclude weak references,为什么抉择 exclude weak references 呢?因为弱援用是不会阻止对象被垃圾回收器回收的,所以咱们这里间接把它排除掉,后果如下图所示:
能够看到,Bitmap 对象通过层层援用之后,到了 MainActivity$LeakClass 这个对象,而后在图标的左下角有个红色的图标,就阐明在这里能够被 GC Roots 拜访到了,并且这是由咱们本人创立的 Thread,并不是 System Class 了,那么因为 MainActivity$LeakClass 能被 GC Roots 拜访到导致不能被回收,导致它所持有的其它援用也无奈被回收了,包含 MainActivity,也包含 MainActivity 中所蕴含的图片。
通过这种形式,咱们就胜利地将内存透露的起因找进去了。这是 Dominator Tree 中比拟罕用的一种剖析形式,即搜寻大内存对象通向 GC Roots 的门路,因为内存占用越高的对象越值得狐疑。
接下来咱们再来学习一下 Histogram 的用法,回到 Overview 界面,点击 Histogram,后果如下图所示:
这里是把以后应用程序中所有的对象的名字、数量和大小全副都列出来了,须要留神的是,这里的对象都是只有 Shallow Heap 而没有 Retained Heap 的,那么 Shallow Heap 又是什么意思呢?就是以后对象本人所占内存的大小,不蕴含援用关系的,比如说上图当中,byte[] 对象的 Shallow Heap 最高,阐明咱们应用程序中用了很多 byte[] 类型的数据,比如说图片。能够通过右键 -> List objects -> with incoming references 来查看具体是谁在应用这些 byte[]。
那么通过 Histogram 又怎么去剖析内存透露的起因呢?当然其实也能够用和 Dominator Tree 中比拟类似的形式,即剖析大内存的对象,比方上图中 byte[] 对象内存占用很高,咱们通过剖析 byte[],最终也是能找到内存透露所在的,然而这里我筹备应用另外一种更适宜 Histogram 的形式。大家能够看到,Histogram 中是能够显示对象的数量的,那么比如说咱们当初狐疑 MainActivity 中有可能存在内存透露,就能够在第一行的正则表达式框中搜寻“MainActivity”,如下所示:
能够看到,这里将蕴含“MainActivity”字样的所有对象全副列出了进去,其中第一行就是 MainActivity 的实例。然而大家有没有留神到,以后内存中是有 11 个 MainActivity 的实例的,这太不失常了,通过状况下一个 Activity 应该只有一个实例才对。其实这些对象就是因为咱们方才一直地横竖屏切换所产生的,因为横竖屏切换一次,Activity 就会经验一个从新创立的过程,然而因为 LeakClass 的存在,之前的 Activity 又无奈被零碎回收,那么就呈现这种一个 Activity 存在多个实例的状况了。
接下来对着 MainActivity 右键 -> List objects -> with incoming references 查看具体 MainActivity 实例,如下图所示:
如果想要查看内存透露的具体起因,能够对着任意一个 MainActivity 的实例右键 -> Path to GC Roots -> exclude weak references,后果如下图所示:
能够看到,咱们再次找到了内存透露的起因,是因为 MainActivity$LeakClass 对象所导致的。
好了,这大略就是 MAT 工具最罕用的一些用法了,当然这里还要揭示大家一句,工具是死的,人是活的,MAT 也没有方法保障肯定能够将内存透露的起因找进去,还是须要咱们对程序的代码有足够多的理解,晓得有哪些对象是存活的,以及它们存活的起因,而后再联合 MAT 给出的数据来进行具体的剖析,这样才有可能把一些暗藏得很深的问题起因给找进去。