一、获取HPROF文件
HPROF文件是MAT能识别的文件,HPROF文件存储的是特定时间点,java进程的内存快照。有不同的格式来存储这些数据,总的来说包含了快照被触发时java对象和类在heap中的情况。由于快照只是一瞬间的事情,所以heap dump中无法包含一个对象在何时、何地(哪个方法中)被分配这样的信息。
这个文件可以使用Memory Profiler导出(详见Android内存优化工具:Memory Profiler)
二、MAT主要界面介绍
得到对应的文件后,要使用Android SDK自带的的工具(hprof-conv 位置在sdk/platform-tools/hprof-conv)进行转换
hprof-conv heap-original.hprof heap-converted.hprof
用MAT打开转换过的HPROF文件是,就有一个弹窗询问展示哪一种报告:
- 内存泄漏报告
自动检查堆转储中是否存在嫌疑内存泄漏,报告哪些对象保持活动以及它们为何没有被垃圾收集器回收 - Component Report
对内存中的对象进行分析并发现内存问题(重复的string,空集合,弱引用等)
选择内存泄漏报告后,打开看的的第一个界面如下图所示:
OverView界面:
我们需要关注的是下面的Actions区域:
-
Histogram:列出内存中的对象,对象的个数以及大小
默认以类名的形式展示,你可以选择不同的方式,有下面四种:
-
Dominator Tree:列出最大的对象以及其依赖存活的Object (大小是以Retained Heap为标准排序的)。注意这个地方是对象而不是类了,这个视图是用来发现大内存对象的。这些对象都可以展开查看更详细的信息
-
Top Consumers : 通过图形列出最大的object
Duplicate Class:检测由多个类加载器加载的类。
-
Thread Overview:展示每个线程的名字、stack、局部变量、Retained Heap等信息
一般Histogram、Dominator Tree、Thread Overview是最常用的
三、MAT中一些概念介绍
Shallow heap
Shallow size就是对象本身占用内存的大小,不包含其引用的对象。
- 常规对象(非数组)的Shallow size有其成员变量的数量和类型决定。
- 数组的shallow size有数组元素的类型(对象类型、基本类型)和数组长度决定
因为不像c++的对象本身可以占大量内存,java的对象成员都是些引用。真正的内存都在堆上,看起来是一堆原生的byte[], char[], int[],所以我们如果只看对象本身的内存,那么数量都很小。所以我们看到Histogram图是以Shallow size进行排序的,排在第一位第二位的是byte,char 。
Retained Heap
Retained Heap的概念,它表示如果一个对象被释放掉,那会因为该对象的释放而减少引用进而被释放的所有的对象(包括被递归释放的)所占用的heap大小。于是,如果一个对象的某个成员new了一大块int数组,那这个int数组也可以计算到这个对象中。相对于shallow heap,Retained heap可以更精确的反映一个对象实际占用的大小
Retained Heap并不总是那么有效。例如我在A里new了一块内存,赋值给A的一个成员变量。此时我让B也指向这块内存。此时,因为A和B都引用到这块内存,所以A释放时,该内存不会被释放。所以这块内存不会被计算到A或者B的Retained Heap中。
为了计算Retained Memory,MAT引入了Dominator Tree。加入对象A引用B和C,B和C又都引用到D(一个菱形)。此时要计算Retained Memory,A的包括A本身和B,C,D。B和C因为共同引用D,所以他俩的Retained Memory都只是他们本身。D当然也只是自己。MAT改变了对象引用图,而转换成一个对象引用树。在这里例子中,树根是A,而B,C,D是他的三个儿子。B,C,D不再有相互关系。把引用图变成引用树,计算Retained Heap就会非常方便,显示也非常方便。对应到MAT UI上,在dominator tree这个view中,显示了每个对象的shallow heap和retained heap。然后可以以该节点位树根,一步步的细化看看retained heap到底是用在什么地方了。
为了纠正这点,MAT中点击右键,可以List objects中选择with outgoing references和with incoming references。这是个真正的引用图的概念,
- outgoing references :表示该对象的出节点(被该对象引用的对象)。
- incoming references :表示该对象的入节点(引用到该对象的对象)。
GC Root
GC发现通过任何reference chain(引用链)无法访问某个对象的时候,该对象即被回收。名词GC Roots正是分析这一过程的起点,例如JVM自己确保了对象的可到达性(那么JVM就是GC Roots),所以GC Roots就是这样在内存中保持对象可到达性的,一旦不可到达,即被回收。通常GC Roots是一个在current thread(当前线程)的call stack(调用栈)上的对象(例如方法参数和局部变量),或者是线程自身或者是system class loader(系统类加载器)加载的类以及native code(本地代码)保留的活动对象。所以GC Roots是分析对象为何还存活于内存中的利器。
四、发现内存泄漏对象
在Histogram或者Domiantor Tree的某一个条目上,右键可以查看其GC Root Path:
点击Path To GC Roots —> exclude all phantom/week/soft etc. references