内存

最近更新时间:2025-11-12 09:53:21

我的收藏
本文主要介绍了平台内存监控模块支持的指标类型、如何开启监控配置以及对各个指标的分析。
下图展示了 Android 和 iOS 平台在内存异常与内存治理方面的联动关系及分析逻辑。


Android

概述

程序开发活动中,内存管理是特别复杂的一项任务,而大多数的疑难 Bug 基本都与内存有关,针对内存问题,可以分为以下三类:内存泄漏、内存滥用、内存访问异常。
内存泄漏:本应该被释放的内存没有被释放,就会造成内存泄漏问题,泄漏发生后,进程的内存会持续增长,最终导致 OOM 等问题。
内存滥用:利用缓存等机制提升程序性能是一种常见的优化手段,但是其有效性一般也要看对缓存对象的管理,稍有异常可能会缓存很多根本用不上的对象,这样不仅不能优化性能,而且还会导致其他模块申请不到内存,从而导致一些内存相关的性能问题,例如 GC 卡顿等。
内存访问异常:内存访问异常,就是我们常见的段错误、内存越界访问这类问题,这一类问题一般都比较难分析,例如内存越界问题,可能在内存越界的当下并没有发生什么异常,只是把某些地址的值写坏了,而当后面其他模块用到这块内存的时候才会触发像段错误这类问题,但由于不知道是谁把内存写坏的,所以即使知道段错误的堆栈一般也无济于事。
内存问题会严重影响用户的基础体验,例如闪退、黑屏、卡顿等,所以对内存问题的监控是很重要的。而因为内存的使用场景频繁、内存滥用等原因,对内存问题的监控和识别较为困难。对此,平台的内存问题的监控主要分为异常监控内存治理两部分。
异常监控:对于一些异常的情况,平台会给出准确的日志信息,用户可以直接提单跟进,例如 Activity 泄漏、内存越界、大图等。
内存治理:提供像 Java 内存详情、FD 详情、Native 内存详情等偏内存治理的能力,例如 Activity 的泄漏链或者内存越界的堆栈等。Java 内存详情、FD 详情等能力会在 Java 内存、FD 资源等快要耗尽的时候,抓取日志信息上报后台,然后分析出疑似的问题,例如超大 Java 对象的引用链或者打开 FD 资源过多的堆栈信息,用户可以通过这些信息直接或者进一步分析得到问题的根因。

指标分析

内存指标是针对大盘用户的统计数据,可以通过指标数据来观察应用整体内存使用情况,另外也可以通过横向或者纵向对比指标,观察内存指标是否有劣化等,目前 Android 的内存监控指标主要包括:内存峰值、FD 触顶率两个数据,可以通过页面左侧内存的 指标分析 页面来查看。


内存峰值

内存峰值,也就是一个进程生命周期里面达到的最大内存占用值,包含 PSSVSSJavaHeap(totalMemory - freeMemory ) 三种分类。任何一台移动设备的可用内存是有限的,当一个进程使用的内存越多,在后台被系统杀掉的风险就越大,GC 导致的性能问题影响也会越多,内存峰值这个指标在一定程度上可以衡量这些问题的影响程度,平台默认会采集主进程的内存峰值数据,如果想要获取子进程的内存峰值,可以在 应用配置 > SDK 配置 的编辑配置页面通过如下配置来开启:

进入到内存的指标分析页面,可以查看内存峰值,包括 PSSVSSJava 堆三种指标,并且支持多种下钻手段。


FD 触顶率

跟内存一样,FD 也是一种有限的资源,一个进程使用 FD 资源超过最大值之后,就无法再继续获取,继而会出现闪退、黑屏等问题,特别是一些低版本的Android,一个进程的可用 FD 资源被限制在1024,这对大型应用来说,1024个 FD 资源是很难满足需求的,所以有必要监控我们进程中 FD 资源的使用,FD触顶率是用来衡量开启 FD 详情功能之后,FD 数目达到一个既定阈值的概率,只有在开启 FD 详情监控这个功能后,才会上报这个指标。


Java 内存泄漏

开启方式

客户端在 初始化 时,添加如下代码。
RumProBuilder builder = new RumProBuilder(appID, appKey);
build.addMonitor(RumProMonitorName.MEMORY_JAVA_LEAK);
应用配置 > SDK 配置 的编辑配置页面,参考如下配置项进行配置。
在后台设置 Java 内存泄漏监控功能的采样率,用户可以通过 Java 内存泄漏 配置来调节用户采样率和事件采样率。
sample_ratio:Java 内存泄漏检测功能的用户采样率,0 所有用户关闭该功能,1 所有用户开启该功能。
event_sample_ratio:Java 内存泄漏检测功能的事件采样率,0 所有内存泄漏的事件都不上报,1 所有内存泄漏的事件都上报。


接口说明

开启 Java 内存泄漏监控功能后,平台会自动对 Activity、Fragment 这两类对象进行监控:
对于 Activity:通过注册 ActivityLifecycleCallback,收集执行了 onDestroy 回调的 Activity 对象。
对于 Fragment:通过注册 FragmentManager.FragmentLifecycleCallbacks,收集执行了 onDestroy 回调的 Fragment 对象。
最后对于收集到的这些已经 destroyed 的对象,会在一个监控线程中进行检测,如果检测多次后,该对象仍未被回收,则会被判定该对象发生了内存泄漏, 除了 Activity 和 Fragment 这两类对象外,还可以通过平台如下接口来监控任意对象。
RumPro.startInspectLeakObj(leakObj);

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

日志说明

为了能在同一份日志里面监控多个泄漏对象,平台会在检测到一个对象泄漏之后,会再 delay 一段时间才会去抓取 hprof 文件,这样就可以在同一份日志里面检测多个泄漏对象,抓取 dump hprof 文件的时候,如果有开启的日志,会有如下 Log 输出。
说明:
可使用 adb 命令筛选抓取 dump hprof 文件时的日志,如有日志输出,说明已成功开启检测: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.z
抓取日志文件后,如果是在 Wi-Fi 环境,则会立即上传,其他情况需要等进程下一次启动之后才会上传,上传时的日志为:
06-09 15:58:39.618 23814 23875 I RMonitor_report_File: url: ******* sub_type: activity_leak

控制台功能说明

Java 内存泄漏的 Web 页面主要包括问题列表问题详情两个页面。
问题列表
通过内存泄漏引用链来作为特征,聚合相同特征的个例,形成 Java 内存泄漏的问题列表。
问题列表支持丰富的筛选条件,具体使用请参见 查询

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


Java 内存详情

如前面所述,Java 内存使用过量后,不仅会触发 GC,引起卡顿、ANR 等问题,更严重会直接导致 OOM 闪退,严重影响用户体验。Java 内存详情是这样一种能力,开启后,会时刻监控当前虚拟机堆内存使用情况,当超过一个预设值时,会自动采集相关日志信息上报后台,这个功能主要提供了如下服务:
实现了类似 MAT 的 "Top Consumers" 的能力,日志信息上报后台后,可以分析出单次上报分析单个大对象密集对象等业务比较关心的问题。不需要用户手动抓取 hprof 文件,然后拖到 PC 端使用 MAT 工具分析,极大的提升了分析效率。
在频繁 GC 或者连续多次达到 Java 堆内存最大值的某个阈值的时候,在移动端通过子进程自动 dump Java 堆内存转储文件;在后台 dump 堆内存转储文件,对用户体验影响非常小。
可以在线使用,同时利用平台已有的翻译能力,翻译类名和成员变量名之后,再智能提取聚类关键特征,将相似的问题聚类到一起;自动翻译和聚类,让用户可以轻松聚焦 Top 问题,解决了 MAT 等工具需要手动翻译的繁琐操作问题。
名词解释:
单次上报分析:目前支持检测泄漏的 Activity 对象。
单个大对象:类比 MAT 的 "Biggest Objects" 功能,即其 Retained Size 大于某一个阈值的单个 Java 对象或者类,该阈值目前由平台后台指定。
密集对象:类比 MAT 的 "Biggest Top-Level Dominator Classes" 功能,即某一 Java 类,虽然其单个对象小,但是其所有的 Java 对象实例的总Retained Size 超过某一个阈值,该阈值目前由平台后台指定。

开启方式

需要升级到 SDK 4.4.2.2之后的版本才支持 Java 内存详情监控功能,客户端需要先执行如下代码,同时在后台调整采样率配置来开启。
1. 客户端在 初始化 SDK 时,添加如下代码。
public static void initRumPro(Context context) {
// 1. 初始化参数预构建,必需设置初始化参数
String appID = "a278f01047"// 【必需设置】在平台应用列表获取应用的 appID
String appKey = "1e5ab6b3-b6fa-4f9b-a3c2-743d31dffe86"// 【必需设置】在平台应用列表获取应用的 appKey
RumProBuilder builder = new RumProBuilder(appID, appKey);
......

// 2. 开启 Java 内存详情
build.addMonitor(RumProMonitorName.MEMORY_JAVA_CEILING);

// 3. 初始化,必需调用
RumPro.init(context, builder);
}
2. 应用配置 > SDK 配置 的编辑配置页面开启如下配置项:
sample_ratio:控制用户采样率,即多少设备会开启这个功能。
event_sample_ratio:控制事件采样率,即发生 Java 内存触顶之后,是否需要上报。
threshold:设置日志文件的抓取时机,90 代表的是在达到堆内存最大值的90%的时候开始抓取日志,堆内存最大值对应 Runtime.getRuntime().maxMemory()。


日志说明

功能开启成功的日志:
07-04 10:xx:xx.xxx 14546 1819 D RMonitor_MemoryCeiling: Start MemoryCeilingMonitor
07-04 10:xx:xx.xxx 14546 1819 D RMonitor_MemoryCeiling: start detect memory ceiling
检测到 Java 内存触顶,开始 dump 日志文件:
07-04 10:xx:xx.xxx 3713 3713 I .example.sdkapp: hprof: heap dump "/storage/emulated/0/Android/data/com.example.sdkapp/files/Tencent/RMonitor/main/Log/dump_LowMemory_24-07-04_10.20.41.hprof" starting...
上报日志文件到平台后台的日志( Wi-Fi 网络下实时上报,其他网络重启后上报):
07-04 10:xx:xx.xxx 14546 1918 I RMonitor_report_File: url: https://xxx.qq.com/v1/xxxx/upload-file?timestamp=1720059647340&nonce=7153357010e6227230d5deb79ce73ed7, sub_type: java_memory_ceiling_hprof

控制台功能说明

当平台后台收到用户的 Java 内存触顶日志后,会通过自研的堆转储文件分析工具分析出单个大对象密集对象单次上报分析等问题,每个问题都会提取其关键特征,然后根据关键特征来做聚类,所以一次上报一般会对应平台的多个 issue,在单个大对象和密集对象的问题列表页面主要包含筛选项、趋势分析、问题列表三项内容,其中:
单个大对象 & 密集对象
在查询区域提供很多的筛选项,其中虚拟机最大堆内存和问题特征可以精确的过滤出特定的 issue 问题,其他筛选项可参见 查询
虚拟机最大堆内存( MB ): 是一个下拉选择框,对应 Runtime.getRuntime().maxMemory() 的值。
问题特征:可以通过 "匹配" 等多种方式来过滤特定特征的 issue。
趋势分析
样本数量:对应问题列表中个例的数量。
还支持影响设备数、影响用户数、启动次数、联网设备数多种指标切换,用户可按需选择。
问题列表:问题列表按照 issue 归类,每个 issue 有自己的关键特征,关键特征主要有三种类型 "引用链" 、"支配树" 、"纯文本" ,引用链是大对象到 GC Root 的最短路径,针对大对象是文件或者线程的,会以文件路径或者线程名字来作为聚类特征。

密集大对象的聚类特征即为该大对象的类名:

单次上报分析
单个大对象和密集对象的问题列表展示的是分析和聚类后的结果,而单次上报分析的问题列表是没有聚类的,用户的每一条上报在问题列表中会单独占一条,但是点开其中一条后,可以展示该次上报里面是否有内存泄漏问题以及所有的单个大对象和密集对象问题,可以很方便的查看某次 Java 内存触顶的详细原因,同时也提供批量下载堆转储 hprof 文件的功能。

问题详情页
单个大对象和密集对象有对应的问题详情页,从该页面我们可以看到大对象的所有 GC Root 引用链和它的内存支配树,为了减少展示的层级,支配树默认只会展示大于父节点大小10%的子节点内容,如果叶子节点是数组,还会继续打印数组的元素,同时在附件 Tab 中,可以下载原始的堆转储 hprof 文件,可以通过 hprof-conv 工具转换后,通过 MAT 等工具本地分析。
引用链:

DominatorTree:


FD 详情

Android 中一个进程可以使用的 FD 数量是有限制的,一旦超过最大值,该进程将无法再分配 FD 资源。如果一个 FD 打开后没有及时关闭,就会导致 FD 泄漏,而 FD 泄漏会导致闪退、黑屏、卡死等问题,所以平台建设了一个能力,监控一个进程的 FD 数量变化。如果达到某一个预设的阈值,会将当前时刻的 FD 详细信息以及每个 FD 的分配函数调用栈信息上报给后台。

开启方式

客户端在 初始化 时,添加如下代码。
RumProBuilder builder = new RumProBuilder(appID, appKey);
build.addMonitor(RumProMonitorName.FD_ANALYZE);
应用配置 > SDK 配置 的编辑配置页面,参考如下配置项进行配置。

sample_ratio:被采样的用户才会开启 FD 详情监控功能,0 所有用户关闭该功能,1 所有用户开启该功能。
event_sample_ratio:被采样的设备发生 FD 触顶之后,上报的概率, 0 所有 FD 触顶的事件都不上报,1 所有 FD 触顶的事件都上报。
threshold:设置 FD 触顶的阈值。例如设置为800,那么当 FD 个数为800的时候会上报日志。

日志说明

//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/dumpN/A_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 详情的 Web 页面和 Java 内存泄漏类似,详情可参见 Java 内存泄漏控制台功能说明

Native 内存详情

Native 内存使用监控一直是 Android 平台上面的难点,Google 自己也出了很多工具,但基本都是基于本地调试的,并且很多工具使用的时候都会遇到很多问题,例如兼容性、闪退等,平台的 Native 内存触顶是拦截进程的内存分配和释放情况,然后抓取内存分配的函数调用栈信息,然后在进程的内存( PSS 或者 VSS)达到一定阈值时,会将这些还没有释放的内存分配和函数调用栈信息上报给后台,用户可以通过平台的 Web 页面来检索和分析这些内存触顶的问题。

开启方式

客户端在 初始化 时,添加如下代码。
RumProBuilder builder = new RumProBuilder(appID, appKey);
build.addMonitor(RumProMonitorName.NATIVE_MEMORY_ANALYZE);
应用配置 > SDK 配置 的编辑配置页面,参考如下配置项进行配置。

sample_ratio:设备采样率,表示允许多少比例的设备开启相关监控项。
event_sample_ratio:事件采样率,用来控制当发生内存触顶时( VSS 或者 PSS 触顶),dump 内存进行数据上报的采样率。

控制台功能说明

Native 内存详情的 Web 页面和 Java 内存泄漏类似,详情可参见 Java 内存泄漏控制台功能说明

大图分析

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

开启方式

客户端在 初始化 时,添加如下代码:
RumProBuilder builder = new RumProBuilder(appID, appKey);
build.addMonitor(RumProMonitorName.MEMORY_BIG_BITMAP);
应用配置 > SDK 配置 的编辑配置页面,参考如下配置项进行配置。
sample_ratio:大图监控功能的用户采样率,0 所有用户关闭该功能,1 所有用户开启该功能。
threshold:大图判定阈值,150就代表150%,当 ( Bitmap 宽度/视图宽度>阈值) 或者 ( Bitmap 高度/视图高度>阈值)时,判定该 Bitmap 是大图。


日志说明

平台检测到大图之后,会尝试立即上报,上报的时候一般会打印如下日志:
06-09 17:00:23.000 31628 31822 D RMonitor_report_Json: url: ***** eventName: BigBitmap, client_identify: ********

控制台功能说明

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

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

字段
说明
图片大小
指解码之后的图片的大小,以像素值为单位,例如:422 * 482。
View 大小
指承载该图片的 View 的大小,以像素值为单位。
图片超标比例
指图片的像素占比 View 大小的比例值,是(图片像素 ÷View 像素)-1 得出增长百分比。
例如:((422482) / (300300)) - 1 = 126.00%。
图片超标大小
是(图片像素总量 - View 像素总量)÷ 1024 得出以 KB 为单位的超标数值。
例如:((4224824) - (3003004)) / 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。
示例解析
以上面问题详情的问题为示例,从页面这个字段,我们可以看到发生图片超标的页面为 “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,如下图所示 :


iOS

概述

内存监控用于监控和衡量 App 在线上的内存使用情况。内存作为设备中的重要资源,与系统内的各种进程共享使用。按照 iOS 系统的策略,在前台过度使用内存面临被系统直接杀死的情况;在后台占用大量的内存资源,也会致使 App 被系统杀死以回收资源给前台 App 使用。鉴于此,在合理有效的使用内存资源尤为重要,因此平台提供了内存监控模块,帮助业务衡量 App 在线上的内存使用情况,并提供必要的诊断信息,以定位和优化部分内存问题。

指标分析

内存指标从整体角度来衡量 App 的内存使用情况,主要包含两个指标。

前后台内存峰值

峰值指 App 在一次进程生命周期中所使用的物理内存(phys_footprint)的最大值。峰值内存越高,意味着 App 在极端情况下使用的内存越多。在前台活跃时,越高的峰值意味着其触碰到内存限制的概率越大;同理在后台时,越高的峰值意味着 App 被系统杀死的概率越大。因此将峰值指标以前后台为区分,定义为前台内存峰值后台内存峰值
前台内存峰值:其值越高,意味着发生 FOOM 的概率越高,一般情况下,其增长趋势(尤其是 P99 )与 FOOM 率呈正相关。
后台内存峰值:SDK 收集到 App 退后台时,最后的内存值。后台内存高,意味着 App 在后台被系统回收的概率越高,因此业务有必要优化 App 在退后台后的内存使用情况。由于 SDK 是进程内收集的指标,因此其值相对于系统真实值而言,相对偏高,更多为参考价值。
峰值指标与其他指标类似,支持以不同的时间粒度和筛选条件进行查询对比,具体操作可参见 接入指南

FOOM 率

FOOM ( Foreground Out Of Memory ),前台内存溢出:我们将 App 在前台触发系统内存限制而被 SIGKILL 信号杀死的情况,定义为 FOOM。
由于 FOOM 是被系统 SIGKILL 信号杀死的闪退,不同于一般的异常引发的 Crash(是进程内逻辑执行触发的异常导致),因此传统意义上的 Crash 率并未包含此部分退出情况。同时,由于进程内部无法直接捕获 SIGKILL 信号,因此无法直接记录到准确的 FOOM 退出事件。
在平台中,FOOM 事件通过收集 App 生命周期中的各类状态,例如是否在前台/后台、是否发生已捕获异常、App 最后使用的内存等信息,综合推断是否发生 FOOM 事件。因此,FOOM 的判定结果,可能包含一部分误判的数据(误判指判别的退出原因不正确,但一定是非正常的退出)。
iOS 系统不仅在内存使用过高时会杀死 App,当设备过热、CPU 调度使用频繁、触发过多的 IO 等情况,都会杀死 App。
说明:
FOOM 率即为 FOOM 发生次数与进程启动次数之间的比值,衡量 FOOM 发生的频率。同时加入了从设备角度和用户角度衡量的指标,即设备 FOOM 率和用户 FOOM 率,二者均是由设备 ID 和用户 ID 去重后计算所得。
虽然包含一定的误判,但其整体依然能衡量 App 因内存过度使用,在前台被系统杀死的严重程度。

内存诊断信息

为了帮助业务优化上述指标,平台在提供指标的同时,提供了相关的诊断信息。诊断信息的主要目的是提供主要内存使用的情况,以此提供给业务分析导致内存过度使用主要原因,以确定如何优化。内存使用问题与一般的 Crash 问题不同,平台收集提供的数据并不是准确导致 FOOM 发生的原因。
说明:
一般 Crash 中,发生问题是程序执行到某一个点遇到异常,因此对于捕获到的调用栈和现场数据就是发生异常的点,分析解决即可。
FOOM 问题的本质是内存资源的过度使用导致,因此仅凭某一次内存分配的记录不能说明其内存使用存在不合理,需要结合 App 中所有的内存分配,找到哪些业务逻辑使用了大量且不合理的内存,再结合其业务需求进行优化。也是出于这个原因,FOOM 内存问题需要收集更多的信息,进而导致了其监控逻辑自身的性能开销。为了避免监控逻辑自身开销影响正常的业务逻辑,平台在内存诊断数据的采集中进行抽样处理(默认1%),故此 FOOM 个例信息在绝大部分个例中是无详细信息的。

VC 内存泄漏

VC 泄漏指 iOS App 中的 UIViewController 及其相关子对象在结束使用(pop、dismiss、remove)后,没有被释放的情况(默认延迟30s检查)。由于 iOS 原生应用中,UIKit 框架将 UIViewController 作为组织 App 各个产品逻辑和页面的基础对象,因此 App 的主要逻辑基本都会围绕 UIViewController 展开。平台会监控所有 UIViewController 的主要行为情况及其相关的指标数据变化。在此基础上,对于结束使用而未释放的 VC 对象进行了异常上报处理,以便业务关注此类 VC 对象是否合理释放,避免无用的持有导致占用大量资源,可以在 应用配置 > SDK 配置 的编辑配置页面通过如下配置来开启:

在部分场景下,出于业务需求,可能会持有部分 VC 对象不释放。平台无法甄别此类情况,因此依然会做上报。若业务不期望此类问题再做上报,可以通过 SDK 配置使用指引添加白名单实现过滤。
与大内存分配类似,VC 泄漏除基础信息外,主要提供 VC 的创建调用栈和名称。

VC 泄漏详情:

需要特殊说明的是,在 VC 泄漏个例中 "泄漏内存" 的值,该值是取对应泄漏 VC pop(dismiss,remove) 时的 phys_footprint 值与其创建时的值的差,若为负数则取0。故此其为一个参考值,并非真实的内存泄漏值。在使用过程中,更多应关注 VC 是否合理持有,不必过于纠结泄漏内存的值。

内存详情

单次上报分析、单个大对象、密集对象指标和 Android 类似,详细可参见 Android 内存详情
上报个例即 SDK 检查到是 FOOM 退出事件后,上报的相关详细信息。对于解决 FOOM 个例问题而言,其提供的信息至关重要。关于 FOOM 个例详细信息和主要的分析方式,可参见 FOOM 个例详情

大块内存分配

大内存分配作为一种辅助手段提供,其主要是监控 App 在运行过程中,异常(一次分配超过指定阈值,默认10M)的内存分配行为。故此,其上报数据更多为参考意义,业务需要关注此类分配是否合理,而非一定为异常问题,可以在 应用配置 > SDK 配置 的编辑配置页面通过如下配置来开启:

在大内存分配中,平台主要提供触发分配的调用栈和分配大小,其他信息则为相关的时间、设备等信息。与其他个例问题类似,平台将上报的数据,按照分配的调用栈进行聚类形成 issue,方便开发者分类跟进问题。

issue 详情: