内存分析工具的使用

Shallow Size和Retained Size

Shallow Size

Shallow Size是指对象自身占用的内存大小,不包括它引用的对象

针对非数组类型的对象,它的大小就是对象与它所有的成员变量大小的总和。当然这里面还会包含一些java语言特性的数据存储单元。

针对数组类型对象,它的大小是数组元素对象的大小总和(由对象类型和数组长度决定)。

Retained Size

Retained Size = 当前对象大小 + 当前对象可直接或间接引用到的对象的大小总和。(间接引用的含义:A->B->C,C是间接引用)。

换句话说,Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存

不过,释放的时候,还要排查被GC Roots直接或间接引用的对象,他们暂时不会被当前为Garbage。

看图理解Retained Size

Retained Size

上图中,GC Roots直接引用了A和B两个对象。

A对象的Retained Size=A对象的Shallow Size。

B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size

这里不包括D对象,因为D对象被GC Root直接引用

如果GC Roots不引用D对象呢?

此时,B对象的Retained Size=B对象的Shallow Size + C对象的Shallow Size + D对象的Shallow Size。

GC Roots

当GC发现通过任何引用链都无法访问某个对象的时候,该对象将被回收,GC Roots是分析这一过程的起点。通常GC Roots是一个当前线程调用栈上的对象(例如方法参数和局部变量),或者是线程自身,或者是系统类加载器加载的类以及Native code(本地代码)保留的活动对象。

内存分析

1. 观察Heap(动态)

在DDMS(Dalvik Debug Monitor Server)内,点击heap按钮,更新统计信息。

我们主要关注两项数据:

  • Heap Size 堆的大小,当资源增加,当前堆的空余空间不够时,系统会增加堆的大小,若超过上限 (例如 64M,视平台和具体机型而定)则会被杀掉。

  • Allocated 堆中已分配的大小,这是应用程序实际占用的内存大小,资源回收后,此项数据会变小

查看操作前后的堆数据,看是否有内存泄漏

2. 利用MAT(Memory Analyzer Tool)分析内存堆(静态)

JVM能够记录下问题发生时系统的部分运行状态,并将其存储在堆转储(Heap Dump)文件中,从而为我们分析和诊断问题提供了重要的依据。

DDMS可以将当前的内存 Dump成一个 hprof格式的文件,MAT 读取这个文件后会给出方便阅读的信息。

可以点击工具栏上的Leak Suspects菜单项来生成内存泄露分析报告,也可以直接点击饼图下方的Reports->Leak Suspects链接来生成报告。

分析三步曲

通常我们会采用下面的“三步曲”来分析内存泄漏问题:

  • 首先,对问题发生时刻的系统内存状态获取一个整体印象。

  • 第二步,找到最有可能导致内存泄漏的元凶,通常也就是消耗内存最多的对象。

  • 接下来,进一步去查看这个内存消耗大户的具体情况,看看是否有什么异常行为。

2.1 Leak Suspects

Leak Suspects中会给出了MAT认为可能出现内存泄漏问题的地方,生成一个内存泄漏的OverView界面,获取一个内存状态的整体印象。

分析内存泄漏的原因。

2.2 Histogram

Histogram: 列出内存中的对象,对象的个数及大小。

它按类名将所有的实例对象列出来,可以点击表头进行排序,在表的第一行可以输入正则表达式来匹配结果。

2.3 Dominator Tree

Dominator Tree (支配树):列出最大的对象以及其依赖存活的Object (大小是以Retained Heap为标准排序的)

Retained Set指的是这个对象本身和他持有引用的对象以及这些引用对象的Retained Set所占内存大小的总和,官方的图解如下所示。

从图中可以看出E的Retained Set为E和G。C的Retained Set为C、D、E、F、G、H。

MAT所定义的支配树就是从上图的引用树演化而来。在引用树当中,一条到Y的路径必然会经过X,这就是X支配Y。X直接支配Y则指的是在所有支配Y的对象中,X是Y最近的一个对象。支配树就是反映的这种直接支配关系,在支配树中,父节点直接支配子节点。下图就是官方提供的一个从引用树到支配树的转换示意图。

C直接支配D、E,因此C是D、E的父节点,这一点根据上面的阐述很容易得出结论。C直接支配H,这可能会有些疑问,能到达H的主要有两条路径,而这两条路径FD和GE都不是必须要经过的节点,只有C满足了这一点,因此C直接支配H,C就是H的父节点。通过支配树,我们就可以很容易的分析一个对象的Retained Set,比如E被回收,则会释放E、G的内存,而不会释放H的内存,因为F可能还引用着H,只有C被回收,H的内存才会被释放。

这里对支配树进行了讲解,我们可以得出一个结论:通过MAT提供的Dominator Tree,可以很清晰的得到一个对象的直接支配对象,如果直接支配对象中出现了不该有的对象,就说明发生了内存泄漏。

2.4 Top Consumers

Top Consumers: 通过图形列出最大的object。

通常,Histogram和 Dominator Tree是最常用的。

2.5 outgoing references和incoming References

outgoing references:表示该对象的出节点(被该对象引用的对象)。

incoming references:表示该对象的入节点(引用到该对象的对象)。

2.6 OQL语句查询

OQL全称为Object Query Language,类似于SQL语句的查询语言,能够用来查询当前内存中满足指定条件的所有的对象。它的查询语句的基本格式为:

SELECT * FROM [ INSTANCEOF ]   <class_name> [ WHERE <filter-expression>]

在Eclipse Memory Tool 上,按OQL,输入select * from instanceof android.app.Activity, 这个指令可以找所有在系统上是android.app.Activity的instance。

关于OQL语句有很多用法,具体可以查看官方文档【http://help.eclipse.org/luna/index.jsp?topic=/org.eclipse.mat.ui.help/reference/oqlsyntax.html】。

如果在object的旁带有“Unknown”的话,那object是可以被Gargage Collect的。要看其他的object是什么原因不可以被Gargage Collect 的话,可以在那个object上right-click,然后选Path to GC Roots, exclude all phantom/weak/soft etc. references。 (这个选项排除了虚引用、弱引用和软引用,这些引用都是可以被VM查到的,所以是可以Gargage Collect的。)

结果中this$0的含义就是内部类自动保留的一个指向所在外部类的引用。

3. Native内存定位方法

调试应用本地内存泄露跟java层内存泄露一样,无法准确判定是哪段代码出现内存泄露,只能定位出疑似内存泄露点,需要结合代码来确定。

像分析java层heap文件一样,先从内存分配较大的代码段入手,结合分配路径和实际代码,判断是否有内存泄露。

具体的方法有:

方法一: 调试log的level

#adb root #adb shell setprop libc.debug.malloc 40 [Default is 0]

设置想要调试的进程 #adb shell setprop libc.debug.malloc.program mm-qcamera-daemon

可选的配置项 #adb shell setprop libc.debug.malloc.minalloclim 10240 [Default is 10KB; values are in Bytes]#adb shell setprop libc.debug.malloc.maxprocsize 31457280 [Default is 30 MB; values are in Bytes]

重启framework层 #adb shell stop #adb shell start

或者重启进程 #adb shell kill <pid_of_mm-qcamera-daemon>

发送debug信号开始抓取 #adb shell kill -28 <pid_of_mm-qcamera-daemon>

调试log的level #adb root #adb shell setprop libc.debug.malloc 40 [Default is 0]

设置想要调试的进程 #adb shell setprop libc.debug.malloc.program mm-qcamera-daemon

可选的配置项 #adb shell setprop libc.debug.malloc.minalloclim 10240 [Default is 10KB; values are in Bytes]#adb shell setprop libc.debug.malloc.maxprocsize 31457280 [Default is 30 MB; values are in Bytes]

重启framework层 #adb shell stop #adb shell start

或者重启进程 #adb shell kill <pid_of_mm-qcamera-daemon>

发送debug信号开始抓取 #adb shell kill -28 <pid_of_mm-qcamera-daemon>

方法二:

adb shell setprop libc.debug.malloc 1

adb shell stop

adb shell start

adb shell am dumpheap -n

会得到一个.hprof文件,需要手动分析。

该文件上半部分是内存分配的代码段,按内存分配大小排序,下半部分是该进程的内存分配映像。

方法三: 使用DDMS

adb shell setprop libc.debug.malloc 1

adb shell stop

adb shell start

然后在DDMS中有个Native Heap的菜单,可以拿到相应的调试数据

参考链接

内存分析工具 MAT 的使用

使用 Eclipse Memory Analyzer 进行堆转储文件分析

Java程序内存分析:使用mat工具分析内存占用

内存分析工具MAT的使用


猜你喜欢

转载自blog.csdn.net/yq5277/article/details/80170121