zoukankan      html  css  js  c++  java
  • Android内存优化(三)详解内存分析工具MAT

    前言

    在这个系列的前四篇文章中,我分别介绍了DVM、ART、内存泄漏和内存检测工具的相关知识点,这一篇我们通过一个小例子,来学习如何使用内存分析工具MAT。

    1.概述

    在进行内存分析时,我们可以使用Memory Monitor和Heap Dump来观察内存的使用情况、使用Allocation Tracker来跟踪内存分配的情况,也可以通过这些工具来找到疑似发生内存泄漏的位置。但是如果想要深入的进行分析并确定内存泄漏,就要分析
    疑似发生内存泄漏时所生成堆存储文件。堆存储文件可以使用DDMS或者Memory Monitor来生成,输出的文件格式为hpof,而MAT就是来分析堆存储文件的。
    MAT,全称为Memory Analysis Tool,是对内存进行详细分析的工具,它是Eclipse的插件,如果用Android Studio进行开发则需要单独下载它,下载地址为:http://eclipse.org/mat/,这篇文章MAT的版本为1.6.1。

    2.生成hpof文件

    2.1 准备内存泄漏代码

    我们需要准备一段发生内存泄漏代码,如下所示。

    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            LeakThread leakThread = new LeakThread();
            leakThread.start();
        }
        class LeakThread extends Thread {
            @Override
            public void run() {
                try {
                    Thread.sleep(60 * 60 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    View Code

    上面的代码是很典型的内存泄漏的例子,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放,关于内存泄漏可以查看Android内存优化(三)避免可控的内存泄漏这篇文章。

    2.2 DDMS生成hpof文件

    生成hpof文件主要分为以下几个步骤:

    1. 在Android Studio中打开DDMS,运行程序。
    2. 在Devices中选择要分析的应用程序进程,点击Update Heap按钮(装有一半绿色液体的圆柱体)开始进行追踪。
    3. 进行可能发生内存问题的操作(本文的例子就是不断的切换横竖屏)。
    4. 点击Dump HPROP File按钮结束追踪,生成并保存hprof文件,如下图所示。

    DDMS生成的hprof文件并不是标准的,还需要将它转换为标准的hprof文件,这样才会被MAT识别从而进行分析,可以使用SDK自带的hprof-conv进行转换,它的路径在sdk/platform-tools中,进入到该路径执行以下语句即可:

    hprof-conv D:efore.hprof D:after.hprof

    其中 D:efore.hprof 是要转换的hprof文件路径,D:after.hprof 则是转换后hprof文件的保存路径。

    2.3 Memory Monitor生成hpof文件

    除了用DDMS来生成hpof文件,还可以用AS的Memory Monitor来生成hpof文件。
    生成hpof文件主要分为一下几个步骤:

    1. 在Android Monitor中选择要分析的应用程序进程。
    2. 进行可能发生内存问题的操作(本文的例子就是不断的切换横竖屏)。
    3. 点击Dump Java Heap按钮,生成hprof文件,如下图所示。

    Memory Monitor生成的hpof文件也不是标准的,AS提供了便捷的转换方式:Memory Monitor生成的hpof文件都会显示在AS左侧的Captures标签中,在Captures标签中选择要转换的hpof文件,并点击鼠标右键,在弹出的菜单中选择Export to standard.hprof选项,即可导出标准的hpof文件,如下图所示。
    QQ截图20170810232344.pngQQ截图20170810232344.png

    3.MAT分析hpof文件

    用MAT打开标准的hpof文件,选择Leak Suspects Report选项。这时MAT就会生成报告,这个报告分为两个标签页,一个是Overview,一个是Leak Suspects(内存泄漏猜想),如下图所示。

    Leak Suspects中会给出了MAT认为可能出现内存泄漏问题的地方,上图共给出了3个内存泄漏猜想,通过点击每个内存泄漏猜想的Details可以看到更深入的分析清理情况。如果内存泄漏不是特别的明显,通过Leak Suspects是很难发现内存泄漏的位置。

    打开Overview标签页,首先看到的是一个饼状图,它主要用来显示内存的消耗,饼状图的彩色区域代表被分配的内存,灰色区域的则是空闲内存,点击每个彩色区域可以看到这块区域的详细信息,如下图所示。

    再往下看,Actions一栏的下面列出了MAT提供的四种Action,其中分析内存泄漏最常用的就是Histogram和Dominator Tree。我们点击Actions中给出的链接或者在MAT工具栏中就可以打开Dorminator Tree和Histogram,如下图所示。

    其中左边第二个选项是Histogram,第三个选项是Dorminator Tree,第四个是OQL,下面分别对它们进行介绍。

    3.1 Dominator Tree

    Dorminator Tree意味支配树,从名称就可以看出Dorminator Tree更善于去分析对象的引用关系。

    图中可以看出Dorminator Tree有三列数据。

    • Shallow Heap:对象自身占用的内存大小,不包括它引用的对象。如果是数组类型的对象,它的大小是数组元素的类型和数组长度决定。如果是非数组类型的对象,它的大小由其成员变量的数量和类型决定。
    • Retained Heap:一个对象的Retained Set所包含对象所占内存的总大小。换句话说,Retained Heap就是当前对象被GC后,从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,可以很清晰的得到一个对象的直接支配对象,如果直接支配对象中出现了不该有的对象,就说明发生了内存泄漏。
    在Dominator Tree的顶部Regex可以输入过滤条件(支持正则表达式),如果是查找Activity内存泄漏,可以在Regex中输入Activity的名称,比如我们这个例子可以输入MainActivity,效果如下图所示。


    Dominator Tree中列出了很多MainActivity实例,MainActivity是不该有这么多实例的,基本可以断定发生了内存泄漏,具体内存泄漏的原因,可以查看GC引用链。在MainActivity一项单击鼠标右键,选择Path To GC Roots,如下图所示。

    Path To GC Roots选项用来表示从对象到GC Roots的路径,根据引用类型会有多种选项,比如with all references就是包含所有的引用,这里我们选择exclude all phantom/weak/soft etc. references,因为这个选项排除了虚引用、弱引用和软引用,这些引用一般是可以被回收的。这时MAT就会给出MainActivity的GC引用链。


    引用MainActivity的是LeakThread,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是MainActivity,这将会导致MainActivity无法被GC。

    3.2 Histogram

    Histogram与Dominator Tree不同的是,Dominator Tree是在对象实例的角度上进行分析,注重引用关系分析,而Histogram则在类的角度上进行分析,注重量的分析。
    Histogram中的内容如下图所示。

    可以看到Histogram中共用四列数据,关于Shallow Heap和Shallow Heap的含义我们在3.1节已经知道了,剩余的 Class Name代表类名,Objects代表对象实例的个数。

    在Histogram的顶部Regex同样可以输入过滤条件,这里同样输入MainActivity,效果如下图所示。

    MainActivity和LeakThread实例各为11个,基本上可以断定发生了内存泄漏。具体内存泄漏的原因,同样可以查看GC引用链。在MainActivity一项单击鼠标右键,选择Merge Shortest Paths to GC roots ,并在选项中选择exclude all phantom/weak/soft etc. references如下图所示。

    Histogram是在类的角度进行分析,而Path To GC Roots是用来分析单个对象的,因此在Histogram无法使用Path To GC Roots查询,可以使用Merge Shortest Paths to GC roots查询,它表示从GC roots到一个或一组对象的公共路径。
    得出的结果和3.1节是相同的,引用MainActivity的是LeakThread,这导致了MainActivity无法被GC。

    3.3 OQL

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

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

    当我们输入select * from instanceof android.app.Activity并按下F5时(或者按下工具栏的红色叹号),会将当前内存中所有Activity都显示出来,如下图所示。


    如果Activty比较多,或者你想查找具体的类,可以直接输入具体类的完整名称:

    select * from com.example.liuwangshu.leak.MainActivity

    通过查看GC引用链也可以找到内存泄漏的原因。关于OQL语句有很多用法,具体可以查看官方文档

    3.4 对比hpof文件

    因为我们这个例子很简单,可以通过上面的方法来找到内存泄漏的原因,但是复杂的情况就需要通过对比hpof文件来进行分析了。使用步骤为:

    1. 操作应用,生成第一个hpof文件。
    2. 进行一段时间操作,再生成第二个hpof文件。
    3. 用MAT打开这两个hpof文件。
    4. 将第一个和第二个hpof文件的Dominator Tree或者Histogram添加到Compare Basket中,如下图所示。
    5. 在Compare Basket中点击红色叹号按钮生成Compared Tables,Compared Tables如下图所示。

    在Compared Tables也有顶部Regex,输入MainActivity进行筛选。

    可以看到MainActivity在这一过程中增加了6个,MainActivity的实例不应该增加的,这说明发生了内存泄漏,可以通过查看GC引用链来找到内存泄漏的具体的原因。

    除了上面的对比方法,Histogram还可以通过工具栏的对比按钮来进行对比:

    生成的结果和Compared Tables类似,我们输入MainActivity进行筛选:

    可以看到第二个hpof文件比第一个hpof文件多了6个MainActivity实例。

    MAT还有很多功能,这里也只介绍了常用的功能,其他的功能就需要读者在使用过程中去发现并积累。

    参考资料
    《Android群英传 神兵利器》
    《Android应用性能优化最佳实践》
    《高性能Android应用开发》
    利用MAT进行内存泄露分析
    Android最佳性能实践(二)——分析内存的使用情况
    Memory Analyzer

  • 相关阅读:
    mysql修改数据表名
    HDU 5742 It's All In The Mind (贪心)
    HDU 5752 Sqrt Bo (数论)
    HDU 5753 Permutation Bo (推导 or 打表找规律)
    HDU 5762 Teacher Bo (暴力)
    HDU 5754 Life Winner Bo (博弈)
    CodeForces 455C Civilization (并查集+树的直径)
    CodeForces 455B A Lot of Games (博弈论)
    CodeForces 455A Boredom (DP)
    HDU 4861 Couple doubi (数论 or 打表找规律)
  • 原文地址:https://www.cnblogs.com/ganchuanpu/p/9321915.html
Copyright © 2011-2022 走看看