• 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
    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
        ====================================
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    解读以上关键信息:

    • 泄漏对象锁造成内存占用是 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();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    除此之外,右键 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 调用路径,去分析如何优化。

  • 相关阅读:
    重塑感知,荣耀金洋!金洋奖两项用户体验奖项公布
    pprof - 在现网场景怎么用
    centos7卸载mongodb数据库
    机试:偶数分解
    人才早已过剩,计算机专业还值得报吗?
    SAP Commerce Cloud Accelerator 的响应式 Theme 介绍
    call、apply 以及 bind 的区别和用法
    xhEditor实现WORD粘贴图片自动上传
    阿里云/腾讯云幻兽帕鲁服务器为什么更新/重启之后,服务器存档没了?
    01人机交互/打开CMD/常见CMD命令/CMD打开QQ并设置环境变量
  • 原文地址:https://blog.csdn.net/hexingen/article/details/126645702