You need to enable JavaScript to run this app.
导航

使用内存泄漏监控和泄漏兜底服务

最近更新时间2024.03.04 10:17:01

首次发布时间2023.12.18 11:35:15

本文以Android App应用为例,介绍App应用接入与使用内存泄漏的完整流程。

步骤一:获取SDK

  1. 在project级别的build.gradle文件中,添加maven地址。

    buildscript {
        repositories {
            maven {
                url "https://artifact.bytedance.com/repository/Volcengine/"
            }
            maven {
                url "https://artifact.bytedance.com/repository/byteX/"
            }
        }
    }
    allprojects {
        repositories {
            maven {
                url "https://artifact.bytedance.com/repository/Volcengine/"
            }
        }
    }
    
  2. 接入应用性能监控全链路版。

    1. 在project级别的build.gradle文件的dependencies中,添加以下代码,接入插件组件。

      classpath "com.volcengine:apm_insight_plugin:1.4.2"
      
    2. 在app module的build.gradle文件的dependencies中,添加以下代码,完成插桩。
      插桩是为了辅助收集启动耗时、页面加载、网络监控的数据,这部分只适用于接入App进行监控的用户,不适用接入SDK进行监控的用户。

      //在文件头添加
      apply plugin: 'apm-plugin'
      // 在dependencies中添加
      implementation 'com.volcengine:apm_insight:1.5.4.cn-rc.1'
      implementation 'com.volcengine:apm_insight_crash:1.5.0'
      

步骤二:初始化SDK并开启监控

  1. 在Application中onCreate中,添加以下代码,初始化性能相关功能。

    注意

    请在主线程中添加初始化性能相关功能的代码。

    //必须放到Application的onCreate里面,会注册监听生命周期,不涉及数据采集和隐私合规问题
    ApmInsight.getInstance().init(application);
    //初始化自定日志,配置自定义日志最大占用磁盘,内部一般配置20,代表最大20M磁盘占用。1.4.1版本开始存在这个api
    VLog.init(this,20);
    
  2. 启动性能监控,开始收集数据。

    注意

    请在用户同意隐私政策后,再调用方法收集数据。

    ApmInsightInitConfig.Builder builder = ApmInsightInitConfig.builder();
    //开启泄露和泄露兜底检测能力,泄露检测和泄露兜底需要分别在平台配置采样
    builder.detectActivityLeak(new IActivityLeakListener() {
        @Override
        public void onActivityLeaked(Activity activity) {
            //activity泄露的回调
       }
    });
    ApmInsight.getInstance().start(builder.build());
    
  3. 在app module的build.gradle文件最外层,添加以下代码,完成插桩。
    启动分析和页面体验相关功能依赖插件插桩,需配置ApmPlugin的whiteList为自己的包名,配置后该目录下的代码会被插桩。

    ApmPlugin {// 是否进行插桩
        enable true// 是否在Debug包插桩,默认不插桩
        enableInDebug true// DEBUG("DEBUG"), INFO("INFO"), WARN("WARN"), ERROR("ERROR");// DEBUG 级别Log会汇总所有被插桩处理的类供查看,路径 app/build/ByteX/ApmPlugin/ApmPlugin_log.txt
        logLevel "DEBUG"// 启动分析开关:监控App启动耗时,需要同时开启pageLoadSwitch
        startSwitch = true// 页面响应开关:监控Activity的生命周期耗时
        pageLoadSwitch = true// 网络监控开关:监控okhttp3的网络请求
        okHttp3Switch = true//插桩显示HttpUrlConnection的网络请求开关
        httpUrlConnectionSwitch = true// 白名单下的包进行插桩,需要填写要插桩类所在的包名,支持前缀配置
        whiteList = ["com"
        ]// 黑名单包下类不进行插桩,可以配置包名和类名,没有可以填空
        blackList = ["com.xxx"
        ]
    }
    

步骤三:完成SDK上报配置

端上开启泄漏检测和泄漏兜底处理开关后,平台也可以控制泄漏检测和泄漏兜底处理,泄漏兜底处理依赖泄漏检测。详细操作步骤,请参见SDK上报配置

  1. 登录应用性能监控全链路版控制台
  2. 单击目标应用下的App端监控
  3. 在控制台左上角选择全部功能 > SDK上报配置
  4. 在筛选区域单击Android系统。
  5. 各模块采样率配置页签下,单击内存优化
  6. 在内存优化配置页面,打开总开关
    总开关打开会根据采样配置上报数据,关闭则整个模块不会上报数据。
    图片
  7. 选择配置项为内存泄漏检测采样率,然后单击创建配置,修改采样率。
  8. 选择配置项为内存泄漏兜底采样率,然后单击创建配置,修改采样率。
    图片
    • 内存泄漏检测采样率 :控制内存泄漏兜底方案是否开启泄露检测
    • 内存泄露兜底采样率:控制内存泄漏兜底方案是否进行泄露兜底

    说明

    请谨慎开启内存泄露兜底采样率,并逐渐增加采样率。兜底操作会把view的背景图置空,防止有不当逻辑在Activity onDestroy后依然有操作view背景图产生异常。

步骤四:配置泄漏事件

端上检测到泄露事件后,如果判断平台配置了泄露事件上报,会把泄露的Activity名称上报到平台,这样平台就可以查看哪个页面泄露的次数较多。但因为泄露引用链需要dump内存才能解析出来,性能影响较大,这里并不不会dump内存快照解析泄露引用链,只是作为泄露的监控指标。具体泄露引用链可以通过接入OOM崩溃时候dump内存解析出泄露排查问题。

  1. 在控制台左上角选择全部功能 > 事件管理

  2. 在筛选区域单击Android系统,然后单击新增事件

  3. 在新建事件页面,完成以下配置,然后单击创建
    图片
    配置项说明:

    配置区域

    配置项

    说明

    示例

    事件信息

    事件名称

    自定义名称,用于标识该事件。不能与已有事件名重复。

    apmplus_activity_leak_monitor

    事件描述

    输入事件描述。

    系统

    选择系统,支持选择Android和iOS。

    Android

    标签

    选择标签。便于查找该事件。

    指标/维度信息

    名称

    输入指标或者维度的名称。

    leak_activity

    类型

    选择类型,支持选择指标和维度。

    • 维度:如果您只想查看自定义数据的上报量,选择自定义维度
    • 指标:您想查看自定义数据的上报量、平均值和PCT值,选择自定义指标

    维度

    描述

    输入对指标或者维度的描述。

    上报配置

    规则名称

    自定义规则名称,用于标识该规则。

    全量采集

    采样率

    配置采样率。
    事件上报量与收费相关,采样率应根据业务量大小合理配置。

    100%

    规则条件

    配置规则,当满足该规则时上报事件。

步骤五:查看泄漏事件

  1. 在控制台左上角选择全部功能 > 事件趋势
  2. 在筛选区域单击Android系统,以及其他业务相关的筛选项。
  3. 在左侧上报量页签中,单击泄漏事件名称apmplus_activity_leak_monitor,选择维度leak_activity,然后在维度列表中单击维度名称leak_activity。
  4. 在详情页面查看leak_activity指标泄漏详情。
    图片

原理说明

如果Android客户端实现的需求有不当的代码实现,就会导致Activity泄漏。Activity的泄漏会牵连一系列的对象,特别是各种View和他们的背景图Bitmap会占据大量的内存,是内存泄漏非常重要的占比。泄漏分析支持检测上报Activity泄露情况,还可以兜底Activity泄露导致的内存泄露,即断掉View对图片的引用,减少泄露内存。

基本概念

概念

说明

什么是内存泄漏?

在计算机中,由于疏忽或错误造成程序未能释放已经不再使用的内存,叫做内存泄漏。
在Java中,存在一些被分配的对象。

  • 这些对象是可达的,即在有向图中,GC ROOT存在通路可以与其相连**。**
  • 这些对象是无用的,即程序以后不会再使用这些对象。

如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

如何检测Activity是泄露的?

一个Activity如果已经执行了onDestroy(),那么他的生命周期已经结束,不应该被任何对象所引用。在下一次GC回收的时候应该把执行了onDestroy()的Activity回收掉。如果执行了GC依然没有被回收,则判定当前Activity有强引用,已经泄露。

如何判断执行了GC?

支持代码主动触发GC,但是频繁GC对APP的整理运行并不友好,可能会造成卡顿。所以支持配置延迟检测Activity泄露,尽可能的让系统自己触发GC回收,而不是人为主动触发。
在Activity onDestroy后时建立一个WeakReference对一个new Object()的引用,如果这个new Object()被回收则认为触发了GC。

如何判断Activity是否被回收?

使用 WeakReference(T referent, ReferenceQueue<? super T> q)包装Activity,在Activity被垃圾回收器回收后,reference会被放入内部的ReferenceQueue中。
也就是说,从队列ReferenceQueue取出来的所有reference,它们指向的真实对象都已经成功被回收了。

什么是内存泄漏兜底?

一些泄漏的对象的生命周期已经结束,但依然存在着强引用,导致对象没有办法被GC回收。通过代码主动断开泄露对象到GC ROOT的链路,或者释放掉泄露对象的引用对象,让这些泄露对象在下次GC时候可以被回收掉,释放出内存空间,这就是内存泄露兜底。它就像一个自动扫地机器人,不断地帮您在房间清理垃圾。

内存泄漏兜底处理哪些对象?

  • 谁占用的内存较大
  • 谁泄露的频率较高
  • 确认兜底后尽量不会有业务逻辑再去访问泄露对象,防止产生空指针异常。

通过延时处理泄露Activity持有View树的背景图和ImageView的图片,在Activity泄露后把背景图和ImageView的图片置空,防止图片泄露。
当然更激进的可以把RootView的所有子View全都Remove掉,但是业务使用复杂,很有可能在Remove后,依然有对View树的处理,产生空指针异常。