Android内存分析工具(三):MAT

  • 如果需要能更精确定位问题的堆转储,可以在应用代码中调用dumpHprofData()来生成堆转储。

  • 主要功能:
    1.Overview标签页,提供一个概览界面。
    2.Histogram视图,MAT最有用的工具之一,它可以列出任意一个类的实例数。查找内存泄露或者其他内存方面问题是,首先看看最有可能出问题的类,这个类有多少个实例是个比较好的选择。它支持使用正则表达式来查找某个特定的类,还可以计算出该类所有对象的保留堆最小值或者精确值。当选择了某条显示条目后,可以通过右击弹出菜单。在诊断内存相关问题时,这个菜单是个非常重要的工具。如果开发者怀疑这里有个内存泄露,可以通过菜单直接查看该类的对象持有哪些其他对象,当然,MAT支持过滤查询结果(比如说限制被持有对象的类型)。查询结果出来时,列表通过另外一个有用的工具-”Path toGC Roots”-展示给开发人员。它支持多种过滤选项,比如说排除弱引用-这是最常见的一个选项,因为当GC运行时,被弱引用持有的对象会被GC直接回收,所以这种对象是不会造成内存泄露的,一般直接把这种信息排除。如果MAT预定义的查询不能满足用户需求的话,它还支持自己定制查询,定制的自由度非常大,拥有无限的可能。
    3.支配树(Dominator Tree),提供程序中最占内存的对象 (described later in the article)。支配树可以算是MAT中第二有用的工具,它可以将所有对象按照保留堆大小排序显示。用户可以直接在“Overview”选项页中点击“Dominator Tree”进入该工具,也可以在上面提到的菜单中选择“immediate dominators”进入该工具。前者显示dump文件中所有的对象,后者会从类的层面上查找并聚合所有支配关系。该界面每一行的最左边都有一个文件型的图标,这些图标有的左下角带有一个红色的点,有的则没有。带有红点的对象就表示是可以被GC Roots访问到的,而可以被GC Root访问到的对象都是无法被回收的。那么这就说明所有带红色的对象都是泄漏的对象吗?当然不是,因为有些对象系统需要一直使用,本来就不应该被回收。我们可以注意到,上图当中所有带红点的对象最右边都有写一个System Class,说明这是一个由系统管理的对象,并不是由我们自己创建并导致内存泄漏的对象。而不带有红点的对象,也可能被 GC 间接持有。总的来说,Dominator Tree中比较常用的一种分析方式,即搜索大内存对象通向GC Roots的路径,因为内存占用越高的对象越值得怀疑。
    4.对象查询语言(Object Query Language Studio), 用来写MAT查询的工具.
    5.专家系统测试(Expert System Test) –
    6.堆Dump概况(Heap Dump Overview) –提供堆dump文件的详细信息
    7.疑似泄露点(Leak Suspects) – 提供内存泄露疑点占用内存大小,被谁加载的,以及类型等详细信息。
    8.Top Components – 提供占内存最多的对象信息,还包括可能的内存浪费信息.
    9.查询浏览器(Query Browser) – 提供很多很有用的查询,有助于内存分析,本文将会介绍最有用的那些查询。根据地址查找对象 – 可以根据提供的一个地址查找某个特定的对象.
    10.对象列表(List Objects) – 显示应用中所有对象,以及这些对象持有哪些其他对象和被哪些其他对象持有,(MAT会提示查询哪一个对象)。
    11.根据类显示对象(Show Objects by Class) – 列出每个类有多少对象.
    12.到GC根节点的路径(Path to GC Roots) – 显示到根节点的引用路径 (有好多过滤选项).
    13.合并到GC根节点的最短路径(Merge Shortest Paths to GC Roots) –找到从GC根节点到一个对象或一组对象的共同路径。
    14.即时支配(Immediate Dominators) – Finds and aggregates on a class level all objects dominating a given set of objects. 在给定的一组对象中,从类的层面上查找并聚合所有支配关系。(【译者注】好吧,我觉得实在有必要说一下支配的意思,支配在计算机的控制流理论中意思是假如说从起始节点到节点B的所有路径都经过节点A,则节点A支配节点B。在垃圾回收理论中应该是指从某个对象在另外一个对象的保留堆中)
    15.显示保留集合(Show Retained Set) – 计算一个对象的保留堆大小.
    16.饼图 – 显示持有内存最大的对象
    17.直方图 – 显示每个类的对象数量
    18.支配树 – 列出所有对象,并按照对象持有的保留堆大小排序
    19.检查器 – 选择一个对象,并显示其详细信息
    20.即时支配(Immediate Dominators) – Finds and aggregates on a class level all objects dominating a given set of objects. 在给定的一组对象中,从类的层面上查找并聚合所有支配关系。(【译者注】好吧,我觉得实在有必要说一下支配的意思,支配在计算机的控制流理论中意思是假如说从起始节点到节点B的所有路径都经过节点A,则节点A支配节点B。在垃圾回收理论中应该是指从某个对象在另外一个对象的保留堆中)
    21.定制查询:如果MAT预定义的查询不能满足用户需求的话,它还支持自己定制查询,定制的自由度非常大,拥有无限的可能。
    22.“Dominator Tree”与“immediate dominators”,前者显示dump文件中所有的对象,后者会从类的层面上查找并聚合所有支配关系。
    23.查询:是用来检查对象树的基本工具,内存分析就是在许多对象中查找不希望看到的引用关系的过程-这件事听上去容易做起来难。如果可以过滤这些对象和应用关系的话可以使这项复杂的运动简单不少。一个开发人员想要成功的调试内存问题,必须掌握两个关键点。第一个是对自己的应用充分了解,如果对自己应用程序中的对象之间的关系不够了解的话,是不能找到内存问题的。第二个是掌握过滤和查找的技巧。如果开发者知道对象结构,而且也可以快速的找到想要的东西,那么找到那些异常状况将会变得容易一些。MAT 有内建查询,我们也可自定义查询。

  • 这里要说一下的是,Retained Heap并不总是那么有效。例如我在A里new了一块内存,赋值给A的一个成员变量。此时我让B也指向这块内存。此时,因为A和B都引用到这块内存,所以A释放时,该内存不会被释放。所以这块内存不会被计算到A或者B的Retained Heap中。为了纠正这点,MAT中的Leading Object(例如A或者B)不一定只是一个对象,也可以是多个对象。此时,(A, B)这个组合的Retained Set就包含那块大内存了。对应到MAT的UI中,在Histogram中,可以选择Group By class, superclass or package来选择这个组。(又开始Histogram中不显示Retained heap,需要点击那个计算器的按钮才会计算出来)。这里最小的粒度是类级别的。

  • 为了计算Retained Memory,MAT引入了Dominator Tree。加入对象A引用B和C,B和C又都引用到D(一个菱形)。此时要计算Retained Memory,A的包括A本身和B,C,D。B和C因为共同引用D,所以他俩的Retained Memory都只是他们本身。D当然也只是自己。我觉得是为了加快计算的速度,MAT改变了对象引用图,而转换成一个对象引用树。在这里例子中,树根是A,而B,C,D是他的三个儿子。B,C,D不再有相互关系。把引用图变成引用树,计算Retained Heap就会非常方便,显示也非常方便。对应到MAT UI上,在dominator tree这个view中,显示了每个对象的shallow heap和retained heap。然后可以以该节点位树根,一步步的细化看看retained heap到底是用在什么地方了。要说一下的是,这种从图到树的转换确实方便了内存分析,但有时候会让人有些疑惑。本来对象B是对象A的一个成员,但因为B还被C引用,所以B在树中并不在A下面,而很可能是平级。为了纠正这点,MAT中点击右键,可以List objects中选择with outgoing references和with incoming references。这是个真正的引用图的概念,表示该对象的出节点(被该对象引用的对象)和入节点(引用到该对象的对象)。

  • Java内存泄露归根结底都是一个原因导致的,应该被释放的对象被生命期更长的对象引用,所以没法被GC。这个生命期更长的对象很常见的是static对象,会持续整个进程。在个人实际工作中,我会先用adb shell dumpsys meminfo查看dalvik heap会不会持续增长。如果是,我会在在dominator Tree中按照Retained Memory排序,找出比较大的(经常是Bitmap),然后用Path to GC Roots看看其引用情况。在这个Path中,一般会发现我们app自己包的类,可以分析这个类是不是还是需要的。如果不需要,那说明可能存在内存泄露。此时,在对这个自己包的类查看incoming references。看看到底是哪些引用导致它没有释放。用这种方法,会比较快的发现问题。另外一个可以减少内存的方法是删除临时不用的内存,也可以使用mat像监测内存泄露一样,看看哪些比较大的内存临时不用却仍然被引用,然后删除对其引用。

  • Histogram:以下图示为分析内存泄露过程

将所有的类列出来

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

  • Histogram对比:

这里写图片描述
如上图,为查找内存泄漏,通常需要两个 Dump结果作对比,打开 Navigator History面板,将两个表的 Histogram结果都添加到 Compare Basket中去

这里写图片描述
如上图,添加好后,打开 Compare Basket面板,得到结果

这里写图片描述
如上图,点击右上角的 ! 按钮,将得到比对结果

这里写图片描述
如上图,为更方便查找差异,可以调整对比选项

这里写图片描述
如上图,再把对比的结果排序,就可得到直观的对比结果

  • MAT中常用查询:

List objects:
With Outgoing References 显示选中对象持有哪些对象.
With Incoming References 显示选中对象被哪些对象持有。[如果一个类有很多不需要的实例,那么可以找到哪些对象持有该对象,让这个对象没法被回收]

Show object by class:
With Outgoing References 显示选中对象持有哪些对象, 这些对象按类合并在一起排序
With Incoming References 显示选中对象被哪些对象持有.这些对象按类合并在一起排序

Path to GC Roots:
With all references 显示选中对象到GC根节点的引用路径,包括所有类型引用.
Exclude weak references 显示选中对象到GC根节点的引用路径,排除了弱引用. [弱引用不会影响GC回收对象]
Exclude soft references 显示选中对象到GC根节点的引用路径,排除软引用(【译者注】软引用持有的对象在内存空间足够时,GC不回收,内存空间足够时,GC回收)
Exclude phantom references 显示选中对象到GC根节点的引用路径,排除虚引用(【译者注】虚引用是最弱的引用,get()总是返回null,当它的对象被GC回收时,GC将reference放在ReferenceQueue中,用户代码当发现这个reference在在ReferenceQueue时就知道它持有的对象已经被回收了,这时可以做一些清理工作。《Java编程思想》第四版,中文版,第87页写到Java的finilize方法是为了对象被回收前做清理工作,但是事实上会有隐患,虚引用正是弥补)

Java Basics:
classloader 该对象对应的classloader信息
find String 在这个对象中查询需要的字符串(还不确定,需要再搞下)
group by 根据某个字段统计出现的个数
References Statistics Class Loader Explorer 显示引用和对象的统计信息,列出类加载器,包括定义的类
Customized Retained Set 计算选中对象的保留堆,排除指定的引用
Open in Dominator Tree 对选中对象生成支配树
Show as Histogram 展示任意对象的直方图
Thread Details 显示线程的详细信息和属性
Thread Overview and Stacks 线程堆栈

Java Collections:
Array Fill Ratio 输出数组中,非基本类型、非null对象个数占数组总长度的比例。
Arrays Grouped by Size 显示数组的直方图,按大小分组
Collection Fill Ratio 输出给定集合中,非基本类型、非null对象个数占集合容量的比例。
Collections Grouped by Size 显示集合的直方图,按大小分组
Extract Hash Set Values 列出指定hash集合中的元素
Extract List Values 列出指定LinkedList,ArrayList或Vector中的元素
Hash Entries 展开显示指定HashMap或Hashtable中的键值对
Map Collision Ratio 输出指定的映射集合的碰撞率
Primitive Arrays With a Constant Value 列出基本数据类型的数组,这些数组是由一个常数填充的。

Leak Identification:
Component Report
Top Consumers 分析可能的内存浪费或者低效使用的组件,并输出最大的那个

  • 使用MAT比较heap dumps
    调试内存泄露时,有时候适时比较2个地方的heap状态是很有用的。这时你就需要生成2个单独的HPROF文件(不要忘了转换格式)。下面是一些关于如何在MAT里比较2个heapdumps的内容(有一点复杂):
    a) 第一个HPROF文件(usingFile>OpenHeapDump).
    b) 打开Histogram view.
    c) 在Navigation Historyview里(如果看不到就从Window>NavigationHistory找).右击histogram然后选择AddtoCompareBasket.
    d) 打开第二个HPROF文件然后重做步骤2和3.
    e) 切换到CompareBasketview,然后点击ComparetheResults(视图右上角的红色”!”图标)。
展开阅读全文

没有更多推荐了,返回首页