前言
在开发过程中,经常遇到测试提出的内存增长明显的测试报告或者是测试提出的OOM问题,此时就需要跟测试获取hprof文件对内存增长和内存泄漏等问题进行排查,那么MAT就是我们必须学会的排查内存增长或泄漏等问题的工具
概念
- MAT:MemoryAnalyzer,内存的分析工具,通过工具可以分析堆内存的使用情况和检测内存泄漏等问题
- hprof文件:指的是当前Android堆内存的快照情况,当前设备的堆内存会全部保存在hporf中,可以通过MAT工具进行打开
分析内存
1、生成hprof文件导出
2、打开MAT 导入我们的2个hprof文件
其中1个是旋转多次屏幕之后的文件(属于内存泄漏部分),另1个是没有内存泄漏的文件,通过以下操作打开:Open File->选择文件->Leak Suspects Report->Finish
在打开文件的时候,经常会遇到Parsing heap dump from xxx has encountered a problem. Error opening heap dump xxx.hprof
这个错误,由于MAT是适用于eclipse的版本,需要将导出来的hprof文件转换一下格式,通过Android SDK自带的工具,在命令行中输入
hprof-conv 1.hprof 2.hprof
3、在OverView视图找到Histogram
4、进入Histogram,查看内存使用情况
该视图以Class类的维度展示每个Class类的实例存在的个数、占用的[Shallow内存]和[Retained内存]大小,可以分别排序显示
- Objects:当前Class的实例存在的个数
- Shallow内存:当前类所有实例所占用的内存大小
- Retained内存:当前对象实例沿着reference chain往下所能收集到的其他类对象实例(可直接或间接引用的)的Shallow Heap总和
5、分别在2个hprof文件中做如下动作,将Histogram视图添加到对比栏
6、点击对比
7、通过比较后就会生成一个比较结果表ComPared Tables,我们通过输入我们自己的包名,找到对应的比较结果
并且在上面的可以设置不同的对比方式
8、通过比较发现我们的程序存在内存泄漏,那么下面就要在内存泄漏的文件中找到泄漏的根源
9、回到泄漏的文件中,找到Histogram入口,输入我们发现泄漏的类名
10、通过右键,查看MainActivity实例被哪些对象使用
- with outgoing references:查看当前对象持有的外部对象引用,从箭头可以看出,当前对象下面还有谁
- with incoming references:查看当前对象被哪些外部对象所引用,从箭头可以看出,当前对象上头还有谁
11、由于使用的对象大多数为系统级别的引用,很难让我们去分辨具体的内存泄漏,所以我们通过遍历GC Root树去将那些有可能被GC回收的实例将他们去除,右键取出可能会被GC的虚/弱/软引用
- Paths to GC Roots:从当前对象到GC roots的调用链,这个调用链解释了为什么当前对象还能存活
- Merge Shortest Paths to GC roots:从当前对象到GC roots的调用链,距离GC Root的最短路径
由于Paths to GC roots
是针对单个对象的,故在Histogram视图无法使用,可以在dominator_tree上用,Merge Shortest Paths to GC roots
针对一个或一组对象,故都可以使用
12、最后只剩下我们泄漏的内存
常用用法
1、Dominator Tree
该视图以实例对象的维度展示当前堆内存中Retained Heap占用最大的对象,以及依赖这些对象存活的对象的树状结构
2、GC Root
在Dominator Tree可以看到视图中的黄点表示当前的GC Root(垃圾回收的根节点),且当前Bitmap对象是被TestActivity
的线程持有。如果当前线程的生命周期和Activity生命周期没有保持一致,那么这里就可能存在内存泄露
在垃圾回收中哪些对象又能被作为GC Root呢?其引用点
- JavaStack中的引用的对象
- 方法区中静态引用指向的对象
- 方法区中常量引用指向的对象
- Native方法中JNI引用的对象
究竟为什么这些对象能成为GC Root引用呢?
- GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收,方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象的引用作为GC Root
- 判断GC Root是否泄漏,则是判断被GC Root引用的对象是不是,不被GC回收的对象,比如,GC Root为静态成员对象,其引用了Activity Context,若管理不当则无法被GC回收,会发生泄漏
3、Immediate Dominator
右键选择Immediate Dominator来查看某个对象被谁直接引用
4、Show Retained Set
右键选择Show Retained Set来查看某个对象调用链里所有直接或间接引用的其他对象,比如查看HashMap的调用链情况
5、Histogram
该视图以Class类的维度展示每个Class类的实例存在的个数、占用的[Shallow内存]和[Retained内存]大小
6、Duplicate Classes
该视图列出被加载多次的类,结果按类加载器进行分组,目标是加载同一个类多次被类加载器加载。使用该工具很容易找到部署应用的时候使用了同一个库的多个版本
7、Top Consumers
列出占用总堆内存超过1%的对象
8、Thread Overview
该视图可以看到:线程对象/线程栈信息、线程名、Shallow Heap、Retained Heap、类加载器、是否Daemon线程等信息
9、Group分组
可以选择以另一种分组方式显示(默认是No Grouping(objects),即以对象维度分组)
10、Find Object by address
通过十六进制的地址查找对应的对象
11、OQL(Object Query Language)
类似SQL查询语言,可以过滤一些想要的内存信息
- 查找包名
select * from com.example.mat.Listener
- 查找size=0并且未使用过的集合
select * from java.util.HashMap where size=0 and modCount=0
select * from java.util.Hashtable where count=0 and modCount=0
select * from java.util.ArrayList where size=0 and modCount=0
- 查找所有的Activity
select * from instanceof android.app.Activity
分析报告
从工具栏入口可以选择MAT默认提供的一些报告工具,有利于我们分析问题
1、Heap Dump Overview
查看整个堆的概括情况,例如:堆内存大小、对象个数、类的个数、类加载器的个数、GC root的个数、堆内存文件的格式、文件的创建时间、位置等信息
2、Leak Suspects
查看潜在的内存泄漏问题
3、Top Components
针对那些占用堆内存超过整个堆内存1%大小的组件做一系列的分析
分析集合
1、分析集合大小
分析集合的大小,可以更合理的去初始化集合的大小,达到性能合理利用
- 筛选目标对象
- 查找被回收时那些将被GC回收的对象集合
- 筛选指定的Object(Hash Map,ArrayList)并按照集合大小进行分组
- 查看哪个对象正在引用当前大小为0的集合
2、分析集合填充率
集合填充率是可以更好反映集合在自身大小确定的情况下,是否被完全填充满,推断出哪些集合具有预分配内存能力,填充率 = size / capacity(容量)
- Collections fill ratio
- 查看填充率
3、分析集合碰撞率
集合碰撞率指的是Hash碰撞,通过减少Hash碰撞冲突能优化集合性能,碰撞率 = 碰撞的实体/Hash表中所有实体
- Map Collision Ratio
- Immediate dominators
4、分析集合键值对
- Hash Entries查看key value
经验总结
1、在查找内存泄漏问题时
通常内存泄漏发生在IO流,内部类,Handler,Activity,Cursor,Receiver等组件中
- 如果是怀疑Activity组件泄漏的时候,可以使用Dominator Tree功能,优先过滤
Activity字段
快速找到是否存在泄漏的对象 - 如果是怀疑Activity组件泄漏的时候,可以通过OQL
select * from instanceof android.app.Activity
并移除所有虚/弱/软引用
2、在查找内存增长问题时
1、Heap内存中占用对象最大的情况
- 可以使用Leak Suspects Report功能,查看工具分析出来的内存占用情况,优先分析报告中提出来的问题
- 可以使用
Dominator Tree
功能,过滤项目包名
,通过Retained Heap排序,快速找到内存占用大的对象 - 找到内存占用最大的对象后,一般是被混淆过后的类名,这时候就要结合mapping,进一步在代码中排查
- 对当前的对象进行查找引用,使用
Paths to GC Roots
的exculde all phantom/weak/soft etc. references
找到真正的强引用对象
2、Native内存中增长的情况(建议Android Studio版本4.2)
- 有时候内存的增长并不是在Heap内存中,在MAT上内存正常,占用比例较小,也不会泄漏
- 此时要怀疑可能内存的增长在Native层,通过将hprof文件拖进Android Studio,找到当前是否有Bitmap等对象占用的Native内存过大
- Android Studio计算的Retained Size是Native+Heap内存的总和,MAT计算的Retained Size是Heap内存的总和
- 如果发现Bitmap占用Native过大,那么在Android Studio就很容易找到
项目对象
占用较大的地方,这里有可能发生泄漏