内存

最近更新时间:2025-10-20 19:00:31

我的收藏
本文主要介绍了平台内存监控模块支持的指标类型、如何开启监控配置以及对各个指标的分析。
下图展示了 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 内存使用过量后,不仅会触发 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:


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 详情: