对于Android这种手持设备来讲,一般不会带有太大的内存,并且通常用户都是长时间不重启手机,因此编写程序的时候必需要很是当心的使用内存,尽可能避免有内存泄露的问题出现。一般分析程序中潜在内存泄露的问题是一件颇有难度的工做,通常都是由团队中的资深工程师负责,并且随着程序代码量的提升,难度还会逐步加大。android
今天要介绍一个在Eclipse中使用的内存分析工具——MAT(Eclipse Memory Analyzer,主页在http://www.eclipse.org/mat/)。它是一个功能很是丰富的Java堆转储文件分析工具,并且简单易用,只需点几下下鼠标就能够生成一个专业的分析报告。更重要的是,它和DDMS可以无缝集成,能够帮助你发现Android程序中潜在的内存泄露问题,同时能够协助你优化程序,更加高效的使用内存。数组
1、安装 MATeclipse
MAT是Eclipse中的一个插件,因此安装很是简单。工具
首先,经过 Help -> Install Software... 启动软件安装页面:优化
选择Add…,会弹出一个对话框,名字(Name)中填MAT(其实随便填什么),位置(Location)中填http://download.eclipse.org/mat/1.4/update-site/,而后点OK:插件
接下来选择你想要安装的 MAT 的功能。由于只须要在Eclipse中使用MAT,因此这里只须要选择Memory Analyzer for Eclipse IDE。特别提一下,Memory Analyzer (Chart) 这个功能是一个可选的安装项,主要用来将数据生成相关的可视化报表,一般这样更加直观,更容易发现问题。线程
剩下的就一路点Next就行了。debug
2、几个基本概念调试
要想看懂MAT生成的报告,还须要掌握几个基本的概念。xml
Java内存回收
Java程序中,内存空间中垃圾回收的工做是由垃圾回收器(Garbage Collector,GC)完成的。它的核心思想是,对虚拟机中堆内存空间中的对象进行识别,若是对象正在被别的对象引用,那么称其为存活对象;反之,若是对象再也不被任何别的对象所引用,则为垃圾对象,能够回收其占据的空间,用于再分配。
对于Android程序来讲,其Dalvik虚拟机自己就是一个类Java的虚拟机实现,也具备主动垃圾回收功能。
GC根对象(GC Root)
在垃圾回收机制中,有一组特殊的对象被称为根对象,它们是一组被虚拟机直接引用的对象。好比,正在运行的线程对象,系统调用栈里面的对象,以及被系统类加载器所加载进来的对象。堆空间中的每一个对象都是由一个根对象为起点被层层引用的,只有它们引用别的对象,没有其它任何对象能够引用它们。所以,若是一个对象还被某一个存活的根元素所直接或间接的引用,就会被认为是存活对象,不能被回收,进行内存释放。
Shallow Size和Retained Size
1)Shallow Size(表面大小)是指对象自己所占用的内存空间大小,不包含被其引用到的对象所占的内存空间。一个对象的Shallow Size取决于这个对象的实例变量的类型和个数。一个数组对象的Shallow Size是数组中保存的对象的Shallow Size乘以数组元素的个数。一个集合对象的Shallow Size是集合内全部对象的Shallow Size之和。
2)Retained Size(保留大小)是指对象自己的Shallow Size加上从其自己开始所能直接或间接访问到的,且只能由其开始才能访问到的全部对象的Shallow Size之和。这个大小就代表,若是将这个对象释放以后,垃圾回收器所能回收的全部内存大小。
下面举个例子说明,假设对象的引用关系以下:
第一张图中计算对象obj1的Retained Size要包含obj一、obj2和obj4,由于obj3和obj5还能够被根对象引用到,因此不能被包含进来。而在第二张图中,obj3只能被obj1间接引用到,因此要包含进来。
支配树(Dominator Tree)
支配树的定义是,若是在对象引用关系图中,从任意一个对象,若是想访问对象Y的话,必须经过对象X,那么对象X就处于对象Y的支配(Dominate)地位。
能够看出来,支配树的概念其实和前面的Retained Size是相关联的。某个对象的Retained Size就是从这个对象开始,以及从它开始全部支配树子节点的Shallow Size之和。
并且,全部根节点必定是支配树中的顶层节点,反之不必定。
堆转储文件(Heap Dump)
所谓堆转储文件,是在一个特定时间点,对Java进程的内存快照文件。它包含了快照被触发时,Java对象和类在堆中的使用状况。因为快照只是一瞬间的事情,因此只能包含在这个时间点各个对象之间的引用关系,而没法知道一个对象在什么时间点,由哪一个方法建立等信息。
MAT其实就是一个通用的Java堆转储文件的分析工具,而经过Android自带的DDMS,能够得到指定设备上指定进程的堆转储文件,将二者结合起来恰好能够达到分析Android程序内存使用情况的目的。
3、如何分析
首先,将你想要分析的程序启动起来,并确保其是可调试的(即在AndroidManifest.xml文件中申明android:debuggable=”true”)。
而后,用DDMS收集Android设备上,你想调试程序的堆转储文件。这步也很简单,在DDMS中,选取你的程序,并点击Dump HPROF file:
DDMS生成转储文件可能要花一点时间,请耐心等待一会。当转储文件生成好了以后,Eclipse会自动调用MAT进行分析,并生成报告:
在这份报告中,咱们能够得到以下信息:
1)Histogram:列出了当前程序中每一个类被实例化出了多少个对象;
2)Dominator Tree:列出了当前程序中各个活动对象间的支配树关系;
3)TopConsumer:列出了当前程序中Retained Size最大的前几个对象等信息;
4)DuplicateClasses:列出了当前程序中是否有相同的类被不一样的类加载器加载的状况。
Histogram和Dominator Tree的区别是分析问题的角度不同,Histogram是站在类的角度上去分析,而Dominator Tree是站的对象实例的角度上去分析,Dominator Tree能够更方便的看出各个对象间的引用关系。
有了这些武器以后,分析内存泄露问题就变得驾轻就熟了,通常的流程以下:
1)在Histogram和Dominator Tree视图中,根据Retained Heap排序,找出排名靠前的几个类和对象。这些都是后面要重点进行分析的对象。
2)每隔必定时间段生成一次MAT分析报告,对比这几回报告,在在Histogram和Dominator Tree视图中找出Retailed Heap不断增大的几个类和对象。持续占用内存,还不释放,一般意味着有潜在的内存泄露问题。
3)固然,若是一个对象的Retained Size很大,并不必定表明它必定是有问题的,还须要具体状况具体分析。具体来讲,咱们能够分析一个对象到根对象的引用路径来分析为何该对象不能被顺利回收。若是说一个对象已经不被任何程序逻辑所须要,可是还存在被根对象引用的状况,基本上就能够说这里存在内存泄露的问题。经过Histogram或者Dominator Tree视图找到疑似有问题的类或者对象以后,选择Path to GC Roots或者Merge Shortest Paths to GC Roots。这里有不少过滤选项,通常来说能够选择exclude all phantom/weak/soft etc.references。这样就排除了虚引用、弱引用、以及软引用,剩下的就是强引用。除了强引用外,其它任何类型的引用,在Java虚拟机须要的状况下,均可以被GC释放掉。若是一个对象始终没法被释放,确定是由于有强引用存在。
4)接下来就须要直接定位具体的代码,看看如何释放这些不应存在的对象。找到缘由,清理干净后,再对照以前的操做,看看对象是否任然持续增加。若是再也不,则说明这个潜在的内存泄露问题已经被修复了。