MAT工具使用

使用MAT工具检测内存泄露问题

                                                                                                                          Loy.ouyang

一、内存泄露的定义

Android 依靠GC(GarbageCollection,垃圾回收)来回收掉一些不再使用的对象,但是GC并不能防止出现内存泄露。对于那些GCRoot可达(仍然存在引用的对象)且无用的对象,GC就无法对其回收,因此造成内存泄露。对于Android这种内存小的设备,内存泄露会严重影响APP体验。

GC机制我就不详细展出来了,简书的这篇还算详细:

       https://www.jianshu.com/p/5db05db4f5ab

二、使用Eclipse MemoryAnalyzer(MAT)工具分析内存泄露

1、获取MAT工具

        MAT工具下载网址:

                 http://www.eclipse.org/mat/downloads.php

  解压后,直接双击打开MemoryAnalyzer.exe即可打开MAT。

2、获取.hprof文件

在使用工具之前,我们使用Android studio生成.hprof文件:

 

打开DDMS界面,操作步骤:

1、在左侧面板中选择你要观察的应用程序进程;

2、点击Update Heap按钮;

3、接着在右侧面板中点击Heap标签,之后操作app,不停地点击Cause GC按钮来实时地观察应用程序内存的使用情况即可;

4、点击DumpHprof files按钮,选择目录存放.hpfor文件

具体如下图:

 


下面我们来模拟一种Activity内存泄漏的场景,内部类相信大家都有用过,如果我们在一个类中又定义了一个非静态的内部类,那么这个内部类就会持有外部类的引用。现在我在com.android.calculator2.Calculator.java程序中加入一段代码:

...
private class LeakClassextends Thread{
   
@Override
   
public void run() {
       
while (true) {
            Log.e(
"Calculator","ouyang");
       
}
    }
}

@Override
protected void onCreate(BundlesavedInstanceState) {
    LeakClass leakClass =
new LeakClass();
   
leakClass.start();
   
...
}
...

 

如果说内部类的存活时间不超过类的存活期,是不会出现泄露,但是如上代码所示,显然内部类一直都存活(死循环),这样就会导致LeakClass一直持有Calculator(Activity)的引用,Calculator就无法释放,从而导致内存泄露。

       我们运行程序,如果一直来回切换Calculator,就会导致前面创建的Activity一直无法释放,那么长时间操作下我们的应用程序所占用的内存就会越来越高,最终出现OutOfMemoryError。

运行程序后,并来回切换主界面,几次之后,然后按下Cause GC(这步一定要做,避免一些可以GC掉的对象因为未GC而扰乱我们的分析,然后点击Dump Hprof files,获取.hprof文件,这个文件记录着我们应用程序内部的所有数据。我把.hprof文件存放在(随意位置都行):

 

C:\Users\loy.ouyang\Desktop\mat分析\hprof\old\com.android.calculator2.hprof

 

但是目前MAT还是无法打开这个文件的,我们还需要将这个但是MAT无法直接打开这个.hprof文件,我们需要使用AndroidSDK\Sdk\platform-tools中的hprof-conv.exe工具将HPROF文件从Dalvik格式转换成J2SE格式。打开cmd终端,使用hprof-conv命令就可以完成转换工作,如下所示:

hprof-convC:\Users\loy.ouyang\Desktop\mat分析\hprof\old\com.android.calculator2.hprofC:\Users\loy.ouyang\Desktop\mat分析\hprof\out\com.android.calculator2.hprof

      

       前一个路径是DDMS生成的.hprof文件,后面的路劲是转化后的.hprof文件路径。

      

       打开MAT工具,点击File->OpenFile…,找到

C:\Users\loy.ouyang\Desktop\mat分析\hprof\out\com.android.calculator2.hprof

       然后加载com.android.calculator2.hprof文件,最后点击finish,加载完成。

       如果没有出来overview界面,就点击最左边的restore按钮展开overview界面。


 MAT中提供了非常多的功能,这里我们只要学习几个最常用的就可以了。上图最中央的那个饼状图展示了最大的几个对象所占内存的比例,这张图中提供的内容并不多,我们可以忽略它。在这个饼状图下就有几个非常有用的工具了,我们来学习一下。

Histogram可以列出内存中每个对象的名字、数量以及大小。

Dominator Tree会将所有内存中的对象按大小进行排序,并且我们可以分析对象之间的引用结构。

一般最常用的就是以上两个功能了,那么我们先从DominatorTree开始学起。

现在点击Dominator Tree,结果如下图所示:

 

1.    最上面一栏ShallowHeapRetain Heap:

 

ShallowHeap: 对象本身占用内存的大小,不包含其引用的对象,没有多少参考价值;

      

RetainHeap: 包含所有引用或被引用对象占用的内存,比Shallow Heap更有参考价值。

      

2.    下面一栏Regex:

<Regex>:可以输入正则表达式去寻找你想要查看的class。

3.    再下来就是各种class条目,右键条目可以看到很多菜单选项,我这里列几条常用的:

Listobjects -> with incoming references:查看这个对象被哪些外部对象引用;

Listobjects -> with outcoming references:查看这个对象持有哪些外部对象引用;

PathTo GC Roots -> exclude all phantim/weak/soft etc.references:查看这个对象的GC Root,不包含虚、弱引用、软引用,剩下的就是强引用。从GC上说,除了强引用外,其他的引用在JVM需要的情况下是都可以 被GC掉的,如果一个对象始终无法被GC,就是因为强引用的存在,从而导致在GC的过程中一直得不到回收,因此就内存溢出了;

PathTo GC Roots -> exclude weak/soft references:查看这个对象的GCRoot,不含弱引用和软引用所有的引用;

MergeShortest path to GC root:找到从GC根节点到一个对象或一组对象的共同路径。

4.    条目最右侧SystemClassThread等字样,是指可能的GC root:

SystemClass:系统Class Loader加载的类. 例如java运行环境中rt.jar中类, 比如java.util.*package中的类;

JNILocal/ JNI Global:JNI 中的本地/全局变量, 用户自定义的JNI代码或是JVM内部的;

ThreadBlock:当前活动线程块引用的对象;

Thread:运行中的线程;

BusyMonitor:调用了wait(),notify(),或者是synchronized。例如,如果调用了synchronized(Object),或者进入了synchronized方法,那么静态方法指的类,非静态方法指的是对象;

JavaLocal:Java本地实例, 比如方法的入参和方法内创建的变量;

NativeStack:native代码里的传入参数或是返回值,比如file/net/IO方法以及反射的参数;

Finalizable:在一个队列里等待它的finalizer 运行的对象;

Unfinalized:一个有finalize方法的对象,还没有被finalize,同时也没有进入finalizer队列等待finalize;

Unreachable:引用到达不了的对象,在MAT里被标记为root用来retain object,否则是不会在分析中出现的;

JavaStack Frame:java栈帧包含了本地变量,当dump被解析时且在preferences里设置过把栈帧当做对象,这时才会产生;

Unknown:未知的Root类型。

 这张图包含的信息非常多,我来带着大家一起解析一下。首先Retained Heap表示这个对象以及它所持有的其它引用(包括直接和间接)所占的总内存,因此从上图中看,前两行的Retained Heap是最大的,我们分析内存泄漏时,内存最大的对象也是最应该去怀疑的。我们点击GroupResult by…按钮(红色横线),选择Package,这样我们就可以按照自己APP的包名去定位,如下图:

 

 另外大家应该可以注意到,在每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,根据上面的讲解,可以被GC Root访问到的对象都是无法被回收的。那么这就说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。我们可以注意到,上图当中所有带红点的对象最右边都有写一个System Class,说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象,无需查看。

 那么上图中就无法看出内存泄漏的原因了吗?确实,内存泄漏本来就不是这么容易找出的,我们还需要进一步进行分析。上图当中,除了带有SystemClass的行之外,最大的就是第一行的CalculatorText对象了,虽然CalculatorText对象现在不能被GC Roots访问到,但不代表着CalculatorText所持有的其它引用也不会被GCRoots访问到。现在我们可以对着第一行点击右键->Path To GC Roots -> exclude allphantim/weak/soft etc.references,我们这里直接把强引用之外的全部排除掉,结果如下图所示:

 

       这里可以清楚的看到,LeakClass持有了CalculatorText对象,Root类型是Thread,就是线程持有对象,导致对象无法回收,造成了泄露。

       通过这种方式,我们成功的找到了内存泄露的原因。这是DominatorTree中比较常用的一种分析方式,即搜索大内存对象通向GCRoots的路径,因为内存占用越高的对象越值得怀疑。

       接下来我们再来学习一下Histogram的用法,回到Overview界面,点击Histogram,结果如下图所示:

 

       图中,我们可以看到类实例对象的个数和大小。那么我们现在就通过Histogram又怎么去分析内存泄漏的原因。我们怀疑主Activity存在泄露,那么直接搜索Calculator,搜索结果如下图:

发现Calculator数目是13个,这太不正常,通常情况下一个Activity只有一个实例才对。接下来对Calculator右键,点开List objects -> with incomingreferences,查看一下到底是哪些外部对象持有了Calculator实例,结果如下图:


我们只需要查看强引用的部分,于是右键一条,选择Path To GC Roots -> exclude allphantim/weak/soft etc.references,结果如下图:


 这里可以清楚的看到,也是LeakClass的Thread持有对象造成的内存泄漏,和之前分析的结果是一样的,内存泄露的原因同样找到了。

       我再介绍几个MAT比较有用的功能菜单,这里我介绍三个,Group菜单,Export菜单,Compare菜单,其他功能可自行研究,菜单位置如下图:


1.     Group Result by…菜单

 回到Overview界面,点击Histogram,点Group Result by…菜单,有Group by ClassGroup by superclassGroup by class loaderGroup by package,举个例子,选择Group by package(按包名列组),如下图:

 点开自己的包名,就可以清楚的看到自己的类里面的对象个数和占用内存大小了,更快的分析自己的应用各个类内存使用情况。

2.     Export菜单

 刚才我们得到了内存泄露的原因所在,如果我们想导出来,就可以按这个按钮,到处类型有Html,Text和CSV类型。

3.     Compare to another heap Dump菜单

 要使用这个菜单功能,必须Open 两个以上.hprof文件。我举个例子,我再打开一个com.android.calculator2_2.hprof文件,然后同样Groupby package,结果如下:

 这是在没有操作apk的情况下得到的.hprof文件,Calculator(Activity)实例只有一个。

        点击Compare菜单,选择com.android.calculator2_2.hprof,结果如下图:


       可以清楚的看到,Calculator对象相对于com.android.calculator2_2.hprof多了12个,这显然是不正常的(其他的类的实例也是多了很多)。0表示两个文件中对应类的实例对象个数相同。

参考文章:

       http://blog.csdn.net/guolin_blog/article/details/42238633

       https://www.jianshu.com/p/5acb7146371b

       http://ju.outofmemory.cn/entry/172684

       http://ju.outofmemory.cn/entry/129445

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值