性能优化工具知识梳理(5) - MAT

性能优化工具知识梳理(1) - TraceView
性能优化工具知识梳理(2) - Systrace
性能优化工具知识梳理(3) - 调试GPU过度绘制 & GPU呈现模式分析
性能优化工具知识梳理(4) - Hierarchy Viewer
性能优化工具知识梳理(5) - MAT
性能优化工具知识梳理(6) - Memory Monitor & Heap Viewer & Allocation Tracker
性能优化工具知识梳理(7) - LeakCanary
性能优化工具知识梳理(8) - Lint

一、概述

内存一直都是性能优化的重点,今天我们主要介绍如何使用Android Studio生成分析hprof报表,并使用MAT分析结果,在介绍之前,首先需要感谢Gracker,本文的分析大多数都是来自于它的这篇文章:

http://www.jianshu.com/p/d8e247b1e7b2

二、获取内存快照并分析

2.1 获取内存快照

为了便于大家理解,我们先编写一个用于调试的单例MemorySingleton,它内部包含一个成员变量ObjectA,而ObjectA又包含了ObjectBObjectC,以及一个长度为4096int数组,ObjectBObjectC各自包含了一个ObjectDObjectD中包含了一个长度为4096int数组,在ActivityonCreate()中,我们初始化这个单例对象。

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }

    private MemorySingleton() {
        objectA = new ObjectA();
    }

}

public class ObjectA {
    private int[] dataOfA = new int[4096];
    private ObjectB objectB = new ObjectB();
    private ObjectC objectC = new ObjectC();
}

public class ObjectB {
    private ObjectD objectD = new ObjectD();
}

public class ObjectC {
    private ObjectD objectD = new ObjectD();
}

public class ObjectD {
    private int[] dataOfD = new int[4096];
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance();
    }
}

Android Studio最下方的Monitors/Memory一栏中,可以看到应用占用的内存数值,它的界面分为几个部分:


我们先点击2主动触发一次垃圾回收,再点击3来获得内存快照,等待一段时间后,会在窗口的左上部分获得后缀为hprof的分析报表:

在生成的hprof上点击右键,就可以导出为标准的hprof用于MAT分析,在屏幕的右边,Android Studio也提供了分析的界面,今天我们先不介绍它,而是导出成MAT可识别的分析报表。

2.2 使用MAT分析报表

运行MAT,打开我们导出的标准hprof文件:


经过一段时间的转换之后,会得到下面的Overview界面,我们主要关注的是Actions部分:

Actions包含了四个部分,点击它可以得到不同的视图:

  • Histogram
  • Dominator Tree
  • Top Consumers
  • Duplicate Classes

在平时的分析当中,主要用到前两个视图,下面我们就来依次看一下怎么使用这两个视图来进行分析内存的使用情况。

2.2.1 Histogram

点开Histogram之后,会得到以下的界面:


这个视图中提供了多种方式来对对象进行分类,这里为了分析方便,我们选择按包名进行分类:

要理解这个视图,最关键的是要明白红色矩形框中各列的含义,下面,我们就以在MemorySingleton中定义的成员对象为例,来一起理解一下它们的含义:

  • Objects:表示该类在内存当中的对象个数
    这一列比较好理解,ObjectA包含了ObjectBObjectC两个成员变量,而它们又各自包含了ObjectD,因此内存当中有2ObjectD对象。
  • Shallow Heap
    这一列中文翻译过来是“浅堆”,表示的是对象自身所占用的内存大小,不包括它所引用的对象的内存大小。举例来说,ObjectA包含了int[]ObjectBObjectC这三个引用,但是它并不包括这三个引用所指向的int[]数组、ObjectB对象和ObjectC对象,它的大小为24个字节,ObjectB/C/D也是同理。
  • Retained Heap
    这一列中文翻译过来是“保留堆”,也就是当该对象被垃圾回收器回收之后,会释放的内存大小。举例来说,如果ObjectA被回收了之后,那么ObjectBObjectC也就没有对象继续引用它了,因此它也被回收,它们各自内部的ObjectD也会被回收,如下图所示:

    因为ObjectA被回收之后,它内部的int[]数组,以及ObjectB/ObjectC所包含的ObjectDint[]数组所占用的内存都会被回收,也就是:
retained heap of ObjectA = shallow heap of ObjectA + int[4096] +retained heap of ObjectB + retained heap of ObjectC

下面,我们考虑一种比较复杂的情况,我们的引用情况变为了下面这样:



对应的代码为:

public class MemorySingleton {
    private static MemorySingleton sInstance;
    private ObjectA objectA;
    private ObjectE objectE;
    private ObjectF objectF;
    public static synchronized MemorySingleton getInstance() {
        if (sInstance == null) {
            sInstance = new MemorySingleton();
        }
        return sInstance;
    }
    private MemorySingleton() {
        objectA = new ObjectA();
        objectE = new ObjectE();
        objectF = new ObjectF();
        objectE.setObjectF(objectF);
    }
}

public class ObjectE {
    private ObjectF objectF;
    public void setObjectF(ObjectF objectF) {
        this.objectF = objectF;
    }
}

public class ObjectF {
    private int[] dataInF = new int[4096];
}

我们重新抓取一次内存快照,那么情况就变为了:


可以看到ObjectERetained Heap大小仅仅为16字节,和它的Shallow Heap相同,这是因为它内部的成员变量objectF所引用的ObjectF,也同时被MemorySingleton中的成员变量objectF所引用,因此ObjectE的释放并不会导致objectF对象被回收。

总结一下,Histogram是从类的角度来观察整个内存区域的,它会列出在内存当中,每个类的实例个数和内存占用情况。

分析完这三个比较重要的列含义之后,我们再来看一下通过右键点击某个Item之后的弹出列表中的选项:

  • List Objects
  • incomming reference表示它被那些对象所引用
  • outgoing则表示它所引用的对象
  • Show objects by class
    和上面的选项类似,只不过列出的是类名。
  • Merge Shortest Paths to GC Roots,我们可以选择排除一些类型的引用:

    Gc根节点的最短路径,以ObjectD为例,它的两个实例对象到Gc Roots的路径如下,这个选项很重要,当需要定位内存泄漏问题的时候,我们一般都是通过这个工具:

2.2.2 dominator_tree

dominator_tree则是通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的。例如下图,只有MemorySingletonRetain Heap的大小被计算出来,而它内部的成员变量的Retain Heap都为0


要获得更详细的情况,我们需要通过它的outgoing,也就是它所引用的对象来分析:

可以看到它的outgoing视图中有两个objectF,但是它们都是指向同一片内存空间@0x12d8d7f0,通过这个视图,我们可以列出那么占用内存较多的对象,然后一步步地分析,看究竟是什么导致了它所占用如此多的内存,以此达到优化性能的目的。

2.3 分析Activity内存泄漏问题

在平时的开发中,我们最容易遇到的就是Activity内存泄漏,下面,我们模拟一下这种情况,并演示一下通过MAT来分析内存泄漏的原因,首先,我们编写一段会导致内存泄漏的代码:


public class MemorySingleton {
    private static MemorySingleton sInstance;
    private Context mContext;
    public static synchronized MemorySingleton getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new MemorySingleton(context);
        }
        return sInstance;
    }
    private MemorySingleton(Context context) {
        mContext = context;
    }
}

public class MemoryActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_memory);
        MemorySingleton.getInstance(this);
    }
}

我们启动MemoryActivity之后,然后按back退出,按照常理来说,此时它应当被回收,但是由于它被MemorySingleton中的mContext所引用,因此它并不能被回收,此时的内存快照为:


我们通过查看它到Gc Roots的引用链,就可以分析出它为什么没有被回收了:

三、小结

通过Android StudioMAT结合,我们就可以获得某一时刻内存的使用情况,这样我们很好地定位内存问题,是每个开发者必须要掌握的工具!


更多文章,欢迎访问我的 Android 知识梳理系列:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容