当前位置: 首页 > news >正文

android Leakcanary/Studio Profiler/MAT 处理内存问题(泄漏和Big超大内存对象)

1.Android Leakcanary 使用

1.1在项目中依赖

根据项目实际情况,选择对应的版本,因项目中暂时不支持android x,因此这里选择leakcanary 2.2版本。

    //放开下边的代码,即可在debug包中使用leakcanary 检测内存泄露
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2'

1.2.运行项目查看内存泄漏信息

在logcat 中,可以查看到泄漏的信息:

2022-08-31 11:14:07.757 32255-10246/com.xxx.miniworld D/LeakCanary: ====================================
    //....
    1174715 bytes retained by leaking objects
    Signature: 4bfb4367c5b0b4b1a7aeb7ff3b45e02cba6a3d87
    ┬───
    │ GC Root: System class
    │
    ├─ android.net.ConnectivityManager classLeaking: NO (a class is never leaking)
    │    ↓ static ConnectivityManager.sInstance
    │                                 ~~~~~~~~~
    ├─ android.net.ConnectivityManager instance
    │    Leaking: UNKNOWN
    │    ↓ ConnectivityManager.mContext
    │                          ~~~~~~~~
    ╰→ org.appplay.minibrowser.BrowserActivity instance
    ​     Leaking: YES (ObjectWatcher was watching this because org.appplay.minibrowser.BrowserActivity received Activity#onDestroy() callback and Activity#mDestroyed is true)
    ​     key = 2354b5c5-3110-4b61-986b-99e7012bf89a
    ​     watchDurationMillis = 107085
    ​     retainedDurationMillis = 102084
    ====================================
    //...
    Build.VERSION.SDK_INT: 31
    Build.MANUFACTURER: OPPO
    LeakCanary version: 2.2
    App process name: com.xxx.miniworld
    Analysis duration: 20828 ms
    Heap dump file path: /storage/emulated/0/Download/leakcanary-com.xxx.miniworld/2022-08-31_11-13-44_012.hprof
    Heap dump timestamp: 1661915647752
    ====================================

解读以上关键信息:

  • 泄漏对象锁造成内存占用是 1174715 bytes,即1.12M。
  • Leaking: YES 是代表此对象存在泄漏
  • gc 调用链:是BrowserActivity被ConnectivityManager的mContext持有,而ConnectivityManager 类对象又是静态的,从而导致BrowserActivity无法被释放,造成内存泄漏。
  • heap dump file(即hprof 内存快照文件)存储:在/storage/emulated/0/Download/leakcanary-com.xxx.miniworld目录下。

当然除开logcat外,当LeakCanary检测到内存泄露以后会弹出通知,我们点击通知即可查看泄漏信息,这里不展开多介绍。

2.Android Studio 查看 Hprof 堆内存快照

通过导出内存泄漏的hprof文件或者通过studio 捕捉的hprof 文件。

2.1Hprof文件包含的信息

  • 您的应用分配了哪些类型的对象,以及每种对象有多少。
  • 每个对象当前使用多少内存。
  • 在代码中的什么位置保持着对每个对象的引用。
  • 对象所分配到的调用堆栈

2.2 Studio 打开Hprof文件

加载hprof文件的方式:studio的底部菜单Profiler->SESSIONS->+号->load from a file->准备好的hprof文件

先来,解读Hprof 文件的工具界面选项,更好的理解内存信息。

第一个选项,查看指定的堆

在这里插入图片描述

  • app heap: 当前app 使用的堆
  • Image heap: 系统启动映像,包含启动期间预加载的类。此处的分配确保绝不会移动或消失
  • zgote heap: 写时复制堆,其中的应用进程是从 Android 系统中派生的

通常只要关注app heap 和jni heap 便可。

第二个选项,选择分配

在这里插入图片描述

  • Arrange by class:根据类名称对所有分配进行分组。这是默认值
  • Arrange by package:根据软件包名称对所有分配进行分组
  • Arrange by callstack:将所有分配分组到其对应的调用堆栈

通常 , 选择 “Arrange by package” 选项 , 这样就可以比较有条理的查找内存中有哪些对象。

第三个选项, 选择展示的类

在这里插入图片描述

  • show all classes: 展示全部类
  • show activity/fragment leak: 展示内存泄漏的activity/fragment
  • show project class: 展示项目中有关的类。

Hprof 中类列表中解读

在这里插入图片描述

  • Class Name: 类名
  • Allocations: 该类全部对象个数(通过 malloc() 或 new 运算符分配的对象数),若出现许多个,可考虑内存泄漏问题
  • Native Size: 该类全部对象在native 层占用的内存(以byte为单位)
  • Shallow Size: 该类全部对象在Java 层占用的内存,不包含其引用对象的内存(以byte为单位)
  • Retained Size:该类全部对象占用的全部内存(包含java和native层,包含其引用对象的内存)

比如,Bitmap 类,该对象是74个 ,在native 层占用是5043106,在java层占用是3700,Bitmap对象占用全部内存是5049374。

2.3查看Activity/Fragment的内存泄漏

先来了解下,在Android中可能引起内存泄漏的原因

  • 长时间引用Activity/Fragment/View/Drawable /其他对象,导致Fragment/Activity被持有无法被释放。
  • Activity中非静态内部类(Runnable),即Activity非静态内部类等同于隐式外部类,间接持有Activity。
  • 对象被缓存/持有时间超出使用范围时间,比如单例中长时间缓存超大对象。

接下来,先点击第三个选项,选择show activity/fragment leak。接着,选择其中的某个activity 双击,会出现Instance List 项。选择其中的一个Instance 双击,展开该Instance Detail 详细情况。接着点击References ,勾选 show nearest gc root only,可以看到Activity/Fragment的调用链。如下图所示:
在这里插入图片描述

先解读下Instance View/Detail 视图

  • Depth 是指GC根节点到所选实例的最短路径的深度,简单来说就是调用链的节数。
  • Native Size: 此对象在native 层占用的内存(以byte为单位)
  • Shallow Size: 此对象在Java 层占用的内存,不包含其引用对象的内存(以byte为单位)
  • Retained Size:此对象占用的全部内存(包含java和native层,包含其引用对象的内存)

Shallow and retained sizes的进一步解读
在这里插入图片描述
在上图中,obj1 的Retained Size是 (obj1+obj2+obj4)总和shallow size。更多详细信息,请阅读Shallow and retained sizes

注意点:这里的Retained Size 与类列表中的Retained Size是不同的,一个是单独类对象的表示,一个是全部类对象的总和表示。

回归到内存泄漏的话题,根据上图调用链分析可知: 当 BrowserActivity被销毁时,BrowserActivity是作为ConnectivityManager的context属性被持有,没有被完全释放,从而导致内存泄漏

解决方式:通过Application来获取各种system severce 对象。

    /**
     * 处理leakcanary 检测到通过Activity创建system service, 容易被持有未释放
     * @param context
     * @return
     */
    private static Context getSafeContext(Context context){
        return context instanceof Application?context:context.getApplicationContext();
    }

除此之外,右键 jump to source ,可以跳转到相匹配的源码中。

2.4查看其他Java 对象的占用可能导致的泄漏

看下Bitmap类的堆信息,选择Retained Size 倒叙发现最大的Bitmap对象占用内存362947(即354k),调用链是在某个Activity中的layout对象(间接持有bitmap),没有释放,如下所示:

在这里插入图片描述

梳理业务发现,该layout 是在加载webview时才嵌在Activity中。明显该Layout对象的持有时间超过需要展示时间。
因此解决办法:在展示其他业务时,释放该layout对象。

看下String类的堆信息,选择Retained Size 倒叙发现最大的String对象 占用内存168152(即164k),调用链是在SharedPreferences中的hashMap 中缓存,没有被释放,如下图所示:

在这里插入图片描述

解决方法:使用腾讯的mmkv 替代android SharedPreferences或者采用file文件存储。

其他的类对象占用,也是一壶画瓢,这里便不再详细介绍。

更多有关资料,参考Android 官方的捕获堆转储

3.使用mat分析hprof 文件

前期准备

根据电脑和安装的jdk版本,选择对应的mat版本。下载地址:https://www.eclipse.org/mat/previousReleases.php

studio 或者Leakcanary的hprof文件是无法被mat 打开的。因此,需要在通过android_sdk/platform-tools/hprof-conv执行转换的命令hprof-conv heap-original.hprof heap-converted.hprof。如下图所示:
在这里插入图片描述

3.1 Leak Suspects 查看内存泄漏情况

通过mat 打开转好后的hprof 内存快照文件后,如下图所示:

在这里插入图片描述
点击Leak Suspects ,查看内存泄漏情况:
在这里插入图片描述

从上面可知,存在Class 和String 占用超大内存。先来看下String问题,点击detail 进一步查看详情。

先来解读下几个关键名词

  • Shallow Heap: 该对象占用的内存,不包含其引用的对象内存。
  • Retain Heap: 释放该对象引用(包含其引用对象)后,可以减少的内存。

List Object项解读

  • with outgoing references是指 这个对象持有了哪个对象
  • with incoming references是指 哪个对象持有了该对象

先来看下哪些对象持有String对象,右键--> List objects-->with incoming references, 如下图所示:

在这里插入图片描述
list_object 视图中,可以清楚看到string 被哪些对象持有。

接下来,查看gc 调用路径:

选择其中一个,右键 path to GCRoot -> exclude all phantom/weak/soft etc. references(去除所有的虚引用,弱引用,软引用) 就会得到调用该类的跟节点gc root。
在这里插入图片描述
接下来进入path2gc视图中:

在这里插入图片描述

从上图可知,该string 对象被sharedPreferences 中的Hasmap 持有,sharedPreferences被某个类和某个数组持有导致,该string 一直无法被释放。

像这种问题,暂时无法解决,只能先跳过。List_object视图中剩余的string 对象,也是一壶画瓢,这里便不再详细介绍。

总结: 查看类对象中内存调用的步骤,先选择类对象->其次,哪个对象持有了该对象(右键--> List objects-->with incoming references)->最后,gc 调用路径( path to GCRoot -> exclude all phantom/weak/soft etc. references)

3.2通过Histogram 搜索查找匹配

搜索Activity 关键字,检索如下所示:
在这里插入图片描述

当前游戏已经销毁了BrowserActivity,但内存快照中仍然存在。很明显,该Activity发生泄漏,查看其gc 调用路径(path to GCRoot -> exclude all phantom/weak/soft etc. references),如下图所示:
在这里插入图片描述

发现,BrowserActivity是作为ConnectivityManager的context属性被持有,没有被完全释放,这个和用上面的Studio Profiler 分析一样,按照上面的处理方式便可解决。

除此之外,还可以检索Fragment中有没有存在泄漏情况。

搜索Bitmap 关键字,检索如下图所示:
在这里插入图片描述

查看BitmapDrawable 对象被哪些对象持有,如下图所示:

在这里插入图片描述

选择其中的一个BitmapDrawable 的gc 调用路径,如下图所示:

在这里插入图片描述

从上图可知BitmapDrawable对象被BrowserActivity中iBrowser 对象持有,而BrowserActivity被ConnectivityManager的context属性被持有。

该问题和上面问题是同一个,解决方式也是一样的。

通常也可以检索包名,去查看下业务层中哪些对象可能存在泄漏,这里便不做过多介绍

3.3.Dominator Tree(支配树) 查看大内存对象

在此视图中列出了每个对象(Object Instance)与其引用关系的树状结构,同时包含了占用内存的大小和百分比,如下图所示:

在这里插入图片描述

通过Shallow Heap 或者Percentage(内存百分比)进行排序,很容易筛选出占用内存较大的对象,如下所示:
在这里插入图片描述

当然也可以选择排序方式,如下图所示:

在这里插入图片描述
接下来,也是选择占用内存大的对象,查看被哪些对象引用或者引用了哪些对象,查看gc 调用路径,去分析如何优化。

相关文章:

  • bp神经网络有哪些模型,bp神经网络有哪些应用
  • Jmeter(116)——写入xls登录案例实战
  • 【云原生 | Kubernetes 系列】---Prometheus的服务发现机制
  • Redis 属于单线程还是多线程?不同版本之间有什么区别?
  • EBS JVM 内存优化攻略
  • 零基础想自学编程,不知道学前端还是后端还是其他,也不知道学哪种编程语言?
  • 跨越技术鸿沟,革新存储产业:华瑞指数云重磅发布下一代软件定义存储产品
  • 触摸控件——键盘录入之RTC录入
  • 遗传算法bp神经网络原理,bp神经网络和遗传算法
  • AVL树详解+模拟实现
  • 【python】(一)字符串基本操作
  • 猿创征文|全方位快速了解事务的4种隔离级别
  • J9数字论:Web3.0对比传统Web2.0的区别
  • 《linux程序设计》笔记第一章
  • Java中的线程池的线程数量如何确定?
  • php的引用
  • 【comparator, comparable】小总结
  • 【附node操作实例】redis简明入门系列—字符串类型
  • 【跃迁之路】【463天】刻意练习系列222(2018.05.14)
  • 【知识碎片】第三方登录弹窗效果
  • canvas绘制圆角头像
  • CentOS7 安装JDK
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • DOM的那些事
  • Elasticsearch 参考指南(升级前重新索引)
  • gulp 教程
  • Hexo+码云+git快速搭建免费的静态Blog
  • iOS小技巧之UIImagePickerController实现头像选择
  • javascript 总结(常用工具类的封装)
  • MySQL主从复制读写分离及奇怪的问题
  • React as a UI Runtime(五、列表)
  • SpingCloudBus整合RabbitMQ
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • Vue全家桶实现一个Web App
  • 等保2.0 | 几维安全发布等保检测、等保加固专版 加速企业等保合规
  • 记录:CentOS7.2配置LNMP环境记录
  • 普通函数和构造函数的区别
  • 前端存储 - localStorage
  • 前端临床手札——文件上传
  • 小试R空间处理新库sf
  • 一个项目push到多个远程Git仓库
  • 正则与JS中的正则
  • postgresql行列转换函数
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​secrets --- 生成管理密码的安全随机数​
  • #考研#计算机文化知识1(局域网及网络互联)
  • (day 12)JavaScript学习笔记(数组3)
  • (LeetCode 49)Anagrams
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (附源码)spring boot球鞋文化交流论坛 毕业设计 141436
  • (附源码)springboot 智能停车场系统 毕业设计065415
  • (切换多语言)vantUI+vue-i18n进行国际化配置及新增没有的语言包
  • .htaccess配置常用技巧