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( * * );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面的代码是很典型的内存泄漏的例子,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放,关于内存泄漏可以查看Android内存优化(三)避免可控的内存泄漏这篇文章。
2.2 DDMS生成hpof文件
生成hpof文件主要分为以下几个步骤:
- 在Android Studio中打开DDMS,运行程序。
- 在Devices中选择要分析的应用程序进程,点击Update Heap按钮(装有一半绿色液体的圆柱体)开始进行追踪。
- 进行可能发生内存问题的操作(本文的例子就是不断的切换横竖屏)。
- 点击Dump HPROP File按钮结束追踪,生成并保存hprof文件,如下图所示。
DDMS生成的hprof文件并不是标准的,还需要将它转换为标准的hprof文件,这样才会被MAT识别从而进行分析,可以使用SDK自带的hprof-conv进行转换,它的路径在sdk/platform-tools中,进入到该路径执行以下语句即可:
hprof-conv D:\before.hprof D:\after.hprof
其中 D:\before.hprof 是要转换的hprof文件路径,D:\after.hprof 则是转换后hprof文件的保存路径。
2.3 Memory Monitor生成hpof文件
除了用DDMS来生成hpof文件,还可以用AS的Memory Monitor来生成hpof文件。
生成hpof文件主要分为一下几个步骤:
- 在Android Monitor中选择要分析的应用程序进程。
- 进行可能发生内存问题的操作(本文的例子就是不断的切换横竖屏)。
- 点击Dump Java Heap按钮,生成hprof文件,如下图所示。
Memory Monitor生成的hpof文件也不是标准的,AS提供了便捷的转换方式:Memory Monitor生成的hpof文件都会显示在AS左侧的Captures标签中,在Captures标签中选择要转换的hpof文件,并点击鼠标右键,在弹出的菜单中选择Export to standard.hprof选项,即可导出标准的hpof文件,如下图所示。QQ截图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文件来进行分析了。使用步骤为:
- 操作应用,生成第一个hpof文件。
- 进行一段时间操作,再生成第二个hpof文件。
- 用MAT打开这两个hpof文件。
- 将第一个和第二个hpof文件的Dominator Tree或者Histogram添加到Compare Basket中,如下图所示。
- 在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
Android内存优化(三)详解内存分析工具MAT的更多相关文章
- Android事件分发机制详解(2)----分析ViewGruop的事件分发
首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别? ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接 ...
- Android事件传递机制详解及最新源码分析——ViewGroup篇
版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...
- day09 详解内存管理机制
""" 今日内容:详解内存管理 1.引用计数 在内存中为了对变量的值进行标记从而方便管理,采用引用计数的方式对变量进行标记. (1)如果变量的值被引用一次,那么该变量的引 ...
- Java内存模型(JMM)详解
在Java JVM系列文章中有朋友问为什么要JVM,Java虚拟机不是已经帮我们处理好了么?同样,学习Java内存模型也有同样的问题,为什么要学习Java内存模型.它们的答案是一致的:能够让我们更好的 ...
- Java程序在内存中运行详解
目录 Java程序在内存中运行详解 一.JVM的内存分布 二.程序执行的过程 三.只有一个对象时的内存图 四.两个对象使用同一个方法的内存图 五.两个引用指向同一个对象的内存图 六.使用对象类型作为方 ...
- 【转贴】内存重要参数详解 RAS CAS
内存重要参数详解 RAS CAS 分类: LINUX 2014-09-12 09:41:58 原文地址:内存重要参数详解 RAS CAS 作者:Reny http://blog.chinaunix.n ...
- Kubernetes K8S之CPU和内存资源限制详解
Kubernetes K8S之CPU和内存资源限制详解 Pod资源限制 备注:CPU单位换算:100m CPU,100 milliCPU 和 0.1 CPU 都相同:精度不能超过 1m.1000m C ...
- Cordova 打包 Android release app 过程详解
Cordova 打包 Android release app 过程详解 时间 -- :: SegmentFault 原文 https://segmentfault.com/a/119000000517 ...
- 给 Android 开发者的 RxJava 详解
我从去年开始使用 RxJava ,到现在一年多了.今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 .而最近这几个 ...
随机推荐
- 工作随笔—Elasticsearch大量数据提交优化
问题:当有大量数据提交到Elasticsearch时,怎么优化处理效率? 回答: 批量提交 当有大量数据提交的时候,建议采用批量提交. 比如在做 ELK 过程中 ,Logstash indexer 提 ...
- (转)Apache从2.2换至2.4httpd.conf的调整笔记(windows环境)
原文:https://www.cnblogs.com/tjws/articles/3469075.html#top 整理一下Windows环境Apache 2.2 改成 Apache 2.4.1后 h ...
- 模板发送java邮件
Creating email content using a templating library The code in the previous examples explicitly creat ...
- for循环输出空心菱形的形状【java】
使用for循环语句输出以下“空心菱形”效果: * * * * * * * * * * * * * * * * 建议优先参考笔者的另一篇文章:<for循环输出菱形的形状[java]> 代码: ...
- ClickHouse之clickhouse-local
一直在慢慢的摸索clickhouse,之前是用rpm包安装的,没有发现clickhouse-local,最近在centos上面编译成功以后发现多了clickhouse-local,那么这个玩意是什么鬼 ...
- jquery的jsonp的使用
客户端 $(function(){ $.ajax({ type: "get", async: false, url: "http://test.com/json_data ...
- 分析windows .net程序dump文件的两种方式
1.WinDbg 按照自己系统版本下载对应windbg(https://debugging.wellisolutions.de/windbg-versions/),win10版本自动下载文件符号,体验 ...
- CAS 无锁式同步机制
计算机系统中,CPU 和内存之间是通过总线进行通信的,当某个线程占有 CPU 执行指令的时候,会尽可能的将一些需要从内存中访问的变量缓存在自己的高速缓存区中,而修改也不会立即映射到内存. 而此时,其他 ...
- Tomcat8源码笔记(六)连接器Connector分析
根据 Tomcat8源码笔记(五)组件Container分析 前文分析,StandardService的初始化重心由 StandardEngine转移到了Connector的初始化,本篇记录下Conn ...
- JavaScript基础回顾一(类型、值和变量)
请看代码并思考输出结果 var scope = 'global'; function f(){ console.log(scope); var scope = 'local'; console.log ...