性能优化工具知识梳理(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
,本文的分析大多数都是来自于它的这篇文章:
二、获取内存快照并分析
2.1 获取内存快照
为了便于大家理解,我们先编写一个用于调试的单例MemorySingleton
,它内部包含一个成员变量ObjectA
,而ObjectA
又包含了ObjectB
和ObjectC
,以及一个长度为4096
的int
数组,ObjectB
和ObjectC
各自包含了一个ObjectD
,ObjectD
中包含了一个长度为4096
的int
数组,在Activity
的onCreate()
中,我们初始化这个单例对象。
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
包含了ObjectB
和ObjectC
两个成员变量,而它们又各自包含了ObjectD
,因此内存当中有2
个ObjectD
对象。 -
Shallow Heap
这一列中文翻译过来是“浅堆”,表示的是对象自身所占用的内存大小,不包括它所引用的对象的内存大小。举例来说,ObjectA
包含了int[]
、ObjectB
和ObjectC
这三个引用,但是它并不包括这三个引用所指向的int[]
数组、ObjectB
对象和ObjectC
对象,它的大小为24
个字节,ObjectB/C/D
也是同理。 -
Retained Heap
这一列中文翻译过来是“保留堆”,也就是当该对象被垃圾回收器回收之后,会释放的内存大小。举例来说,如果ObjectA
被回收了之后,那么ObjectB
和ObjectC
也就没有对象继续引用它了,因此它也被回收,它们各自内部的ObjectD
也会被回收,如下图所示:
因为ObjectA
被回收之后,它内部的int[]
数组,以及ObjectB/ObjectC
所包含的ObjectD
的int[]
数组所占用的内存都会被回收,也就是:
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];
}
我们重新抓取一次内存快照,那么情况就变为了:
可以看到
ObjectE
的Retained 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
则是通过“引用树”的方式来展现内存的使用情况的,通俗点来说,它是站在对象的角度来观察内存的使用情况的。例如下图,只有MemorySingleton
的Retain 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 Studio
和MAT
结合,我们就可以获得某一时刻内存的使用情况,这样我们很好地定位内存问题,是每个开发者必须要掌握的工具!
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:http://www.jianshu.com/p/fd82d18994ce
- 个人主页:http://lizejun.cn
- 个人知识总结目录:http://lizejun.cn/categories/
网友评论