Android 使用 MAT 查找内存泄漏

对于大型 Java 应用程序来说,再精细的测试也难以堵住所有的漏洞,即便在测试阶段进行了大量卓有成效的工作,很多问题还是会在生产环境下暴露出来,并且很难在测试环境中重现。在没有发现或者并不知道哪有内存泄漏的情况下,可以使用在 3.3 节提到的一系列内存分析工具去分析。通常情况下,可以使用 Heap View 粗略查看堆的使用情况,又或者使用Allocation Tracker 跟踪内存分配情况,当发现内存持续上涨并没有释放时,说明有内存泄漏的可能性,这时再深入分析这个场景的内存情况。Android 虚拟机能够记录下问题发生时,系统的部分运行状态和内存使用情况,并将其存储在堆转储(Heap Dump)文件中,而这个文件为开发者分析和诊断问题提供了重要的依据。

抓取这个疑似有内存问题的使用场景的 Heap 信息,然后进行分析,目前来看最好的分析 JavaHeap 工具就是 MAT。Memory Analyzer Tool(MAT)是一个快速、功能丰富的 Java Heap 分析工具,通过分析 Java进程的内存快照 HPROF 文件,从众多的对象中分析,快速计算出在内存中对象的占用大小,查看哪些对象不能被垃圾收集器回收,并可以通过视图直观地查看可能造成这种结果的对象。通常大家理解的内存泄漏分析被认为是一件很有难度的工作,一般由团队中的资深工程师负责。不过,在这里要介绍的 MAT(Eclipse Memory Analyzer)被认为是一个“傻瓜式”的堆转储文件分析工具,和其他内存泄漏分析工具相比,MAT 的使用非常容易,基本使用几次就能理解,即使是新手,也能够很快上手使用。MAT 工具可以帮助开发者定位导致内存泄漏的对象,以及发现大的内存对象,然后解决内存泄漏并通过优化内存对象,达到减少内存消耗的目的。

  • MAT使用步骤
  • 下载 MAT 客户端:下载地址为 https://eclipse.org/mat/downloads.php,在这里可以下载不同的版本
  • 获取 HPROF 文件:从 Android Studio 进入 Android Device Monitor(DDMS),选择需要分析的应用进程,单击Update Heap 按钮,对应用进行怀疑有内存问题的操作,也可以整体操作一段时间,结束操作后,多主动进行几次 GC,最后单击 Dump HPROF File 按钮,保存 HPROF 文件,如图所示。
    在这里插入图片描述
  • 因为Android Studio保存的是Android Dalvik格式.hprof文件,所以需要转换成J2SE HPROF格式才能被 MAT 识别和分析。Android SDK 自带一个转换工具 hprof-conv,转换语句如下:./hprof-conv path/file.hprof exitPath/heap-converted.hprof其中 path 为转换前的文件路径,exitPath 为转换后文件的路径。
  • 通过 MAT 工具打开转换后的 HPROF 文件,进入分析界面。获取 HPROF 文件和转换有更快捷的方式是在 Memory Monitor 工具中,单击 Dump Java Heap 按钮,在左侧的 Capture 栏中的 Heap Snapshot 列表中看到 Dump 下来的 HPROF 文件,右击文件,在弹出的菜单中选择 Export to standard.hprof 选项,即可转换成标准的 HPROF 文件,再使用 MAT 打开。
  • MAT 视图

使用 MAT 打开 HPROF 文件后,可以看到 MAT 的分析内存视图如下:

在这里插入图片描述

在 MAT 窗口上,OverView 是一个总体概览,显示总体的内存消耗情况和疑似问题。MAT 提供了多种分析维度,其中 Histogram、Dominator Tree、Top Consumers 和 Leak Suspects 的分析纬度不同。

分析内存最常用的是 Histogram 和 Dominator Tree 两个视图,这两个视图的区别是统计的纬度不一样,但使用 Dominator Tree 可以更方便地看出其引用关系。下面介绍几个常用的分析数据视图和报告。

  • Histogram:列出内存中的所有实例类型对象、对象的个数以及大小,并支持正则表达式查找。
  • Dominator Tree:列出最大的对象及其依赖存活的 Object。分析流程和 Histogram 大同小异,但 Dominator Tree能更方便地看出引用关系。
  • Top Consumers:通过图形列出最大的 Object。
    Leak Suspects:通过 MAT 自动分析泄漏的原因和泄漏的一份总体报告。Leak Suspects 列出了工具怀疑的内存泄漏点,以及泄漏的内存大小,在后面有问题列表和所有对象,单击对应的可以看到更深入的分析情况

一般都在 Histogram 或者 Dominator Tree 视图中分析内存是否异常,如图所示。一共有四列:Class Name、Objects、Shallow Heap 和 Retained Heap。
在这里插入图片描述

  • Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。非数组的常规对象的Shallow Heap Size 由其成员变量的数量和类型决定,数组的 Shallo 常规对象的 Shallow HeapSize 由其成员变量的数量和类型决定,数组的 Shallow Heap Size 由数组元素的类型(对象类型、基本类型)和数组长度决定。真正的内存都在堆上,看起来是一堆原生的 byte[]、char[]、int[],对象本身的内存都很小。因此 Shallow Heap 对分析内存泄漏意义不是很大。
  • Retained Heap:是当前对象大小与当前对象可直接或间接引用到的对象的大小总和,包括被递归释放的。也可以理解为,Retained Size 就是当前对象被 GC 后,从 Heap 上总共能释放掉的内存大小。
  • 查找内存泄漏具体位置

为了了解查找泄漏的过程,制造一个内存泄漏,在 Activity 中把一个 TextView 一直让一个单例执有,这样这个对象就会一直得不到释放。根据前面的介绍,拿到 HPROF 文件使用 MAT打开,进入 Dominator Tree 视图。

右击选中实例类型 android.widget.TextView,选择 Merge Shortest Paths to GC Root→exclude all phantom/weak/soft etc refereneces.如图
在这里插入图片描述
在这里插入图片描述
可以看到TextView是被单例所引用,不会被回收。

  • 这个例子很简单,所以很容易找到泄漏的对象,但这种分析方法并不容易确定所有的内存泄漏场景,还需要通过代码的逻辑找出原因。另一种更快速的方法就是对比 HPROF 数据。通过对比两个 Dump 结果来发现内存泄漏的对象,步骤如下:

1)进入需要分析的应用页面,按前面介绍的抓取 HPROF 的方法拿到第一个 HPROF。
2)进行一段时间的操作,再抓取第二个 HPROF 文件。
3)使用 MAT 打开两个 HPROF 文件。
4)在两个 HPROF 文件中,把 Histogram 或者 Dominator Tree 增加到 Compare Basket
5)在Compare Basket 中单机对比按钮,生成对比结果视图。这样就可以对比相同的对象在不同的阶段的对象实例个数和内存占用大小。如果不应该增加的对象数量增加了,说明发生了内存泄漏。如下图:

  • 右键单击dominator_tree
    在这里插入图片描述
  • 选择 Add to Compare Basket
  • 两个都选择完毕之后点击 Compare Basket 中的对比按钮
    在这里插入图片描述
  • 在Compare Tables查看对比结果
    在这里插入图片描述
  • 我是进入界面然后退出再GC,正常退出以后不应该再有TextView的引用如下图:
    在这里插入图片描述
    右键会提示 Table2 按照之前说的方法跳转到merge_shortest_paths
    在这里插入图片描述
    还是这个TextView被单例引用的内存泄漏问题。我们就可以去代码中查看并修改了。
发布了119 篇原创文章 · 获赞 28 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/ldxlz224/article/details/100545171