Android进阶——性能优化之MAT的使用

前言

在开发过程中,经常遇到测试提出的内存增长明显的测试报告或者是测试提出的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呢?其引用点

  1. JavaStack中的引用的对象
  2. 方法区中静态引用指向的对象
  3. 方法区中常量引用指向的对象
  4. Native方法中JNI引用的对象

究竟为什么这些对象能成为GC Root引用呢?

  1. GC管理的主要区域是Java堆,一般情况下只针对堆进行垃圾回收,方法区、栈和本地方法区不被GC所管理,因而选择这些区域内的对象的引用作为GC Root
  2. 判断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组件泄漏的时候,可以通过OQLselect * from instanceof android.app.Activity并移除所有虚/弱/软引用

2、在查找内存增长问题时

1、Heap内存中占用对象最大的情况

  • 可以使用Leak Suspects Report功能,查看工具分析出来的内存占用情况,优先分析报告中提出来的问题
  • 可以使用Dominator Tree功能,过滤项目包名,通过Retained Heap排序,快速找到内存占用大的对象
  • 找到内存占用最大的对象后,一般是被混淆过后的类名,这时候就要结合mapping,进一步在代码中排查
  • 对当前的对象进行查找引用,使用Paths to GC Rootsexculde 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就很容易找到项目对象占用较大的地方,这里有可能发生泄漏
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许英俊潇洒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值