Skip to main content

内存

一、内存指标

1.1、内存峰值

内存峰值,也就是一个进程生命周期里面达到的最大内存占用值,任何一台移动设备的可用内存是有限的,一个进程使用的内存越多,GC导致的性能问题影响也会越大,被系统kill掉的风险也就越大,内存峰值这个指标在一定程度上可以反映这些问题的影响程度,Bugly默认统计了主进程的内存峰值,可以从 数据总览 页面或者 内存里面的指标分析 页面看到内存峰值指标。

除了首页的数据总览里面看内存峰值,也可以进入到内存页面的指标分析一栏,指标分析页面有更多的下钻维度,可以查看不同维度下的内存峰值指标,例如分Android版本来查看,或者过滤某一个进程的内存峰值;

Bugly默认抓取主进程的内存峰值,子进程的内存峰值需要单独配置。

二、Java内存泄漏功能

2.1、功能简介

内存泄漏是指本该被垃圾回收器回收的对象,一直无法被回收,从而造成内存的浪费,导致应用运行变慢甚至崩溃。开启功能后,Bugly自动对Activity、Fragment这两类对象进行了监控:

Java内存泄漏功能简介
  • 对于Activity:通过注册ActivityLifecycleCallback,收集执行了onDestroy回调的Activity对象;
  • 对于Fragment:通过注册FragmentManager.FragmentLifecycleCallbacks,收集执行了onDestroy回调的Fragment对象;

最后对于收集到的这些已经destroyed的对象,会在一个监控线程中进行检测,如果检测多次后,该对象仍未被回收,则判定该对象发生了内存泄漏。

2.2、接口说明

2.2.1、用户配置

初始化Bugly专业版SDK之后,Java内存泄漏监控功能的用户采样率为0.01,用户可以通过 Java内存泄漏 配置来调节用户采样率和事件采样率

  • sample_ration: Java内存泄漏检测功能的用户采样率,0 所有用户关闭该功能, 1 所有用户开启该功能;
  • event_sample_ratio: Java内存泄漏检测功能的事件采样率, 0 所有内存泄漏的事件都不上报, 1 所有内存泄漏的事件都上报;

2.2.2、平台配置

  • enable_fragment_inspect: 是否开启Fragment对象的泄漏监控,默认值为true;
  • loop_max_count: 一个对象最多轮询多少次之后,才认为是泄漏对象;
  • hprof_strip_switch: hprof裁剪开关,可选值如下所示, 多个值可以形成组合关系,默认值为9, 也就是只裁剪除byte和char其他基本类型的数组;
/**
* hprof裁剪相关开关定义
*/
public static final int HPROF_STRIP_ENABLE = 1; // 是否开启裁剪功能
public static final int HPROF_STRIP_BYTES = 1 << 1; // 是否开启 bytes 数组裁剪
public static final int HPROF_STRIP_CHAR = 1 << 2; // 是否开启 char 数组裁剪
public static final int HPROF_STRIP_OTHER_PRIMITIVE = 1 << 3; // 是否开启其他基本类型数组裁剪
public static final int HPROF_STRIP_ZYGOTE_HEAP = 1 << 4; // 是否开启 zygote heap裁剪
public static final int HPROF_STRIP_IMAGE_HEAP = 1 << 5; // 是否开启 image heap裁剪

2.2.3、监控任意Java对象是否泄漏

/**
* 设置Java内存泄漏待检测对象
*
* @param leakObj 待检测对象
*/
public static void startInspectLeakObj(Object leakObj) {
......
}

Java泄漏对象功能除了能检测Activity和Fragment对象是否泄漏之外,还可以监控任意Java对象,例如针对某些占用内存比较多的对象,可以在认为它即将要释放的时候 (例如切换账号之后,理论上某个对象必然要释放),调用如下代码就可以让Bugly来检测该对象是否真的泄漏:

Bugly.startInspectLeakObj(leakObj);

2.2.4、日志抓取和上传

为了能在同一份日志里面监控多个泄漏对象,Bugly会在检测到一个对象泄漏之后,delay一段时间之后再抓取hprof文件,dump hprof文件的时候,如果有开启Bugly的日志,会有如下Log输出:

日志

adb logcat -s RMonitor_MemoryLeak_LeakInspector RMonitor_Heap_MemoryDumpHelper

检测对象是否泄漏的日志:

06-08 21:03:03.700 25492 25535 D RMonitor_MemoryLeak_LeakInspector: Inspecting com.example.test.memory.TestActivityLeak@73988249-e8b3-4f5b-a880-01f41cb7dc3c Time=1686229383700 count=1
06-08 21:03:04.032 25492 25535 D RMonitor_MemoryLeak_LeakInspector: Inspecting com.example.test.memory.
......
06-08 21:04:33.796 25492 25535 D RMonitor_MemoryLeak_LeakInspector: Inspecting com.example.test.memory.TestActivityLeak@73988249-e8b3-4f5b-a880-01f41cb7dc3c Time=1686229473796 count=19
06-08 21:04:38.968 25492 25535 D RMonitor_MemoryLeak_LeakInspector: Inspecting com.example.test.memory.TestActivityLeak@73988249-e8b3-4f5b-a880-01f41cb7dc3c Time=1686229478968 count=20
06-08 21:04:44.138 25492 25535 D RMonitor_MemoryLeak_LeakInspector: Inspecting com.example.test.memory.TestActivityLeak@73988249-e8b3-4f5b-a880-01f41cb7dc3c Time=1686229484138 count=21

DUMP HPROF文件时候的日志:

06-08 21:07:31.882 25492 29639 D RMonitor_Heap_MemoryDumpHelper: ReportLog dumpHprof: com.example.test.memory.LeftFragment@51fa088b-b1f2-464c-8d70-972a1f26c360
06-08 21:07:33.791 25492 29639 D RMonitor_Heap_MemoryDumpHelper: dump used 1885 ms
06-08 21:07:35.304 25492 29639 D RMonitor_Heap_MemoryDumpHelper: leakFlag=true,ZipFile=true,leakName=com.example.test.memory.LeftFragment@51fa088b-b1f2-464c-8d70-972a1f26c360,dumpPath=/storage/emulated/0/Android/data/com.example.sdkapp/files/Tencent/RMonitor/main/Log/dump_com.example.test.memory.LeftFragment@51fa088b-b1f2-464c-8d70-972a1f26c360_leak_23-06-08_21.07.33.zip

抓取的日志文件不会立马上传,需要等进程下一次启动之后才会上传,上传时候的日志为:

06-09 15:58:39.618 23814 23875 I RMonitor_report_File: url: ******* sub_type: activity_leak

2.3、Web页面介绍

Java内存泄漏的Web页面主要包括问题列表和问题详情两个页面:

2.3.1、问题列表

  • 通过内存泄露引用链来作为特征,聚合相同特征的个例,形成Java内存泄露的问题列表。
  • 问题列表支持丰富的搜索条件,两组条件的对比分析,帮助用户聚焦严重的泄露问题,或者最近新增的泄露问题。

问题列表页

2.3.2、问题详情

  • 问题详情,清晰展示泄露引用链,支持用户下载泄露时的内存快照进行详细分析。

问题详情页

三、大图监控功能

3.1、功能简介

所谓大图,也叫图片过度解码,是指解码后的Bitmap的宽高大于承载其的View宽高的情况, 大图监控通过注册ActivityLifecycleCallback,当DecorView发生onGlobalLayout的时候,遍历DecorView树的所有View,检查每个View的背景图或者src图对应的Bitmap的宽高是否大于View的实际宽高,如果是的话,判定该Bitmap是大图(过度解码)。

3.2、接口说明

3.2.1、用户配置

初始化Bugly专业版SDK之后,大图监控功能默认的用户采样率为0.01,用户可以通过 大图分析 配置来调节用户采样率以及大图的判断阈值:

  • sample_ratio: 大图监控功能的用户采样率, 0 所有用户关闭该功能, 1 所有用户开启该功能;

  • threshold: 大图判定阈值,150就代表150%,当 (Bitmap宽度/视图宽度>阈值) 或者 (Bitmap高度/视图高度>阈值)时,判定该Bitmap是大图;

3.2.2、日志抓取和上传

Bugly检测到大图之后,会尝试立即上报,上报的时候一般会打印如下的日志

06-09 17:00:23.000 31628 31822 D RMonitor_report_Json: url: ***** eventName: BigBitmap, client_identify:  ********

3.3、Web页面介绍

3.3.1、问题列表

  • 通过提取过度解码图片的View的布局层次来作为特征聚合问题;
  • 支持图片超标大小和图片超标比例排序,快速定位解码超标严重的图片;

问题列表

3.3.2、问题详情

  • 用户可以根据场景信息,以及展示View的布局层次来找到对应的业务场景。

问题详情

字段说明

图片大小: 422 * 482,是指解码之后的图片的大小,以像素值为单位;

View大小: 是指承载该图片的View的大小,以像素值为单位;

图片超标比例 = ((422 482) / (300 300)) - 1 = 126.00% , 也就是图片的像素占比View大小的比例值;

图片超标大小 = ((422 482 4) - (300 300 4)) / 1024 = (813616 - 360000) / 1024 = 442.98 KB.

视图类型: 这个字段有两个值,background代表是调用View.getBackground获取的背景图片资源, source代表是通过调用View的getDrawable获取的解码图片资源;

页面: 从上面的截图来看,这个case出现的页面是在 com.example.memory.TestBitmapActivity,页面也就是图片超标的View所在的Activity,这个是不可以自定义的;

场景: 还有一个场景字段,场景字段默认也是当前的Activity,但是该字段的值,业务是可以自定义的, 通过 Bugly.enterScene 和 Bugly.exitScene 来自定义当前的场景,例如当前Activity如果有多个Fragment的切换,那么切换Fragment之后,可以通过场景字段来定位切换到了哪个Fragment。

enterScene 和 exitScene 的使用说明可见文档 进入/退出自定义场景

3.3.3、示例解析

还是以上面问题详情这个问题为示例,从页面这个字段,我们可以看到发生图片超标的页面为 “com.example.memory.TestBitmapActivity”, 另外页面这个字段还可以搭配场景字段来使用,这样可以更加精准的定位问题出现时候的上下文信息。

然后从从堆栈详情这里可以看到过度解码的是一个AppCompatImageView,并且可以看出View的层级关系

com.android.internal.policy.DecorView
android.widget.LinearLayout[0] // 这里的0代表的是DecorView的第一个子View
android.widget.FrameLayout[1] // 类似的,这里的1说明的是LinearLayout的第二个子View
android.support.v7.widget.FitWindowsLinearLayout[0]
android.support.v7.widget.ContentFrameLayout[1]
android.widget.ScrollView[0]
android.widget.LinearLayout[0]
android.support.v7.widget.AppCompatImageView[4] // 最终大图所在的View为android.widget.LinearLayout的第四个子View

它是LinearLayout的第四个子View,那么我们就可以通过Android Studio的 Layout Inspector 等工具来定位到这个View,如下图所示:

四、FD详情

Android中一个进程可以使用的FD数量是有限制的,一旦超过最大值,该进程将无法再分配FD资源。遇到需要使用FD资源的地方,就有可能导致崩溃、黑屏等问题,FD详情功能可以监控一个进程的FD数量变化,如果达到某一个预设的阈值,会将当前时刻的fd详细信息以及每个fd的分配函数调用栈信息上报给后台。 FD触顶 FD触顶

开启方式

  • 客户端修改
public static void initBugly(Context context) {
// 1. 初始化参数预构建,必需设置初始化参数
String appID = "a278f01047"; // 【必需设置】在Bugly 专业版 注册产品的appID
String appKey = "1e5ab6b3-b6fa-4f9b-a3c2-743d31dffe86"; // 【必需设置】在Bugly 专业版 注册产品的appKey
BuglyBuilder builder = new BuglyBuilder(appID, appKey);

......

// 2. 开启FD详情
build.addMonitor(BuglyMonitorName.FD_ANALYZE);

// 3. 初始化,必需调用
Bugly.init(context, builder);
}

  • 配置调整
用户采样率:被采样的用户才会开启FD详情监控功能;

事件采样率:被采样的设备发生FD触顶之后,上报的概率;

FD触顶阈值:当进程使用的FD资源个数达到该阈值后,认为是一次FD触顶事件, 该事件最终是否上报,由时间采样率来决定;

FD详情配置

相关日志

1、FD详情监控功能开启成功的日志

05-16 16:33:38.737 19194 11716 I RMonitor_FdLeak_Monitor: fd leak monitor started.

2、检测到FD触顶时刻的日志
05-16 16:33:48.815 19194 11716 I RMonitor_FdLeak_Trigger: top fd: FdStatisticItem{type=8, count=1654}

3、dump 上报信息的日志
05-16 16:33:48.863 12359 12359 I .example.sdkapp: hprof: heap dump "/storage/emulated/0/Android/data/com.example.sdkapp/files/Tencent/RMonitor/main/fd_leak/dump_root/heap.hprof" starting...


4、上报日志(WiFi情况下实时上报,其他情况进程重启后上报)
05-16 16:37:17.697 19195 13956 I RMonitor_report_File: url: https://rmonitor.qq.com/v1/*********/upload-file?timestamp=1715848637695&nonce=44ef8384331c486250d430b78a29a18a, sub_type: fd_leak

注意事项

开启FD详情监控功能后会拦截所有的FD资源开启和关闭行为,会有一定的性能消耗,建议可以先在本地或者灰度版本上面使用.