本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

RSS 内存泄漏排障全攻略(上篇)

从"内存失踪案"到"破案高手",带你搞定 RSS 内存泄漏


前言

       你有没有遇到过这样的场景:App 用着用着就闪退了,日志里赫然写着 "RssThresholdKiller"——系统觉得你的应用"吃内存"太多,直接给了一个"强制劝退"。这背后,就是 RSS 内存泄漏在作祟。

RSS(Resident Set Size)内存泄漏,简单来说就是应用占用的物理内存超过了系统允许的阈值,在整机内存紧张时,系统会果断终止这个"内存大户"。听起来很抽象?别急,本文会用通俗的语言,带你从 RSS 内存的基本概念聊起,一步步教你如何在运维态和开发态快速定位内存泄漏根因,让你的应用告别"闪退尴尬"。


一、什么是 RSS 内存泄漏?先搞懂"内存账本"

       想象一下,你的手机内存就像一个有限的"钱包",每个 App 都在里面花钱。RSS 内存,就是这个 App 实际从钱包里掏出来、正在花的那部分钱——专业术语叫"常驻物理内存"。而 Swap RSS,则是 App 暂时把一些不常用的钱存进了"银行"(磁盘交换区),需要时再取出来。

应用 RSS 内存总值 = 常驻 RSS + Swap RSS。

       那什么叫"内存泄漏"呢?通俗地说,就是 App 一直在花钱(申请内存),却忘了还钱(释放内存)。久而久之,钱包里的钱被掏空,系统一看:"这 App 也太能花了!"——于是直接终止进程,这就是 RSS 内存泄漏故障。

RSS 内存泄漏的常见原因有三类:

       • 因 bug 导致动态分配的内存未释放:就像借了东西不还,越积越多。比如 malloc 了一块内存却忘记 free,或者new了一个对象却没有对应的delete。

       • 业务实现不合理导致内存占用过大:设计时就埋下了隐患。比如缓存策略没有设置上限,数据量增长后内存占用直线上升。

       • 业务过于复杂导致内存占用过大:功能越多,内存压力越大。多个模块各自为政地申请内存,缺乏统一的内存管理和回收机制。

二、怎么查看 RSS 内存?两招教你"查账"

2.1 什么是RSS内存?

       RSS(Resident Set Size,常驻内存)是操作系统分配给进程的物理内存中,当前实际驻留于RAM(物理内存)的那部分容量。简单说,就是你应用"实实在在"占用的内存。

       • 常驻RSS内存:操作系统分配给进程的物理内存中,当前实际驻留于RAM的那部分容量。这是你应用"真金白银"占用的内存。

       • Swap RSS 内存:指进程被换出到磁盘交换区的内存页大小。当物理内存紧张时,操作系统会将不活跃的内存页换出至磁盘,以释放RAM空间。这部分被换出的内存即为Swap RSS 内存。

       • 应用RSS内存总值:应用RSS内存总值=应用进程常驻RSS值+应用进程Swap RSS值。这是系统判断你是否"超标"的总帐本。

       • RSS内存泄漏故障:当应用RSS内存总值大于一定阈值时,系统则认为应用对内存使用超过合理范围,存在内存泄漏。系统整机低内存时会主动终止发生了内存泄漏的应用进程,并上报RSS内存泄漏事件。

2.2 RSS内存大小获取方式

       想知道你的 App 到底花了多少内存?系统提供了两种"查账"方式。

方法一:通过命令行读取 /proc/{pid}/status

// bash
cat /proc/<PID>/status | grep -E "^(VmRSS|VmSwap|Rss)"
// 执行后输出示例:
// VmRSS: 6572 kB ← 常驻 RSS 内存
// RssAnon: 2272 kB ← 匿名页 RSS
// RssFile: 4288 kB ← 文件映射 RSS
// RssShmem: 12 kB ← 共享内存 RSS
// VmSwap: 1484 kB ← Swap RSS 内存

       这条命令就像给 App 做了一次"体检",VmRSS 告诉你它实际占了多少物理内存,VmSwap 告诉你它有多少内存被换到了磁盘上。

方法二:通过 PerformanceAnalysisKit 的 hidebug.getRssInfo() 接口

       你也可以使用 hidebug.getRssInfo() 接口,它返回当前进程的 RSS 和 Swap 内存占用信息,方便你在应用内部做内存水位监控。当然,处于性能考虑,还是建议使用方法一中读取/proc/{pid}/status节点的方式监控当前应用的内存占用。

三、系统是怎么"抓"到内存泄漏的?

       系统在应用 RSS 内存申请路径上"暗中观察",一旦发现某个应用的 RSS 内存总值超过了系统阈值,就会判定该应用发生了 RSS 内存泄漏。但系统不会一上来就"动手",它有两个管控条件:

          • 应用 RSS 内存大于一定阈值,超出合理使用范围

          • 整机进入低内存状态

       💡 提示:低端设备的内存总量较小,更容易进入低内存状态。整机压力影响因素较多,应用需要关注自身内存是否超阈值,只要超出阈值,就需要进行相关优化,提升应用保活成功率和良好的使用体验。

四、故障感知:让系统"主动告诉你"出事了

       那么如何知道自己发生了RSS内存泄漏甚至被系统管控了呢?这就要推荐开发者订阅以下两类故障事件了:

4.1 订阅资源泄漏事件

       当应用触发 RSS 内存泄漏故障后,系统会通过 HiAppEvent 发送 RESOURCE_OVERLIMIT 事件回调。这个事件里包含了丰富的内存信息,包括 RSS 总量、NativeHeap、ArkTSHeap 等子类型内存数据,还会附带 Smaps 等维测日志路径。

// json
// RESOURCE_OVERLIMIT 事件回调示例
HiAppEvent onReceive: domain=OS
HiAppEvent eventName=RESOURCE_OVERLIMIT
HiAppEvent eventInfo={
  "domain": "OS",
  "name": "RESOURCE_OVERLIMIT",
  "eventType": 1,
  "params": {
    "bundle_name": "com.example.myapplication",
    "bundle_version": "1.0.0",
    "memory": {
      "pss": 2100257, // PSS 内存(kB)
      "rss": 1352644, // RSS 内存(kB)
      "sys_avail_mem": 250272, // 系统可用内存
      "sys_free_mem": 60004, // 系统空闲内存
      "sys_total_mem": 1992340, // 系统总内存
      "vss": 2462936 // VSS 虚拟内存
    },

    "pid": 20731,
    "resource_type": "pss_memory",
    "time": 1502348798106,
    "uid": 20010044,
    "external_log": [
      "/data/storage/el2/log/resourcelimit/RESOURCE_OVERLIMIT_1725614572401_6808.log"
    ],
    "log_over_limit": false
  }
}

       注意 external_log 字段——它指向了系统抓取的维测日志路径,这是后续分析的关键线索。

4.2 订阅应用终止事件

       如果应用被系统终止了,可以通过订阅 APP_KILLED 事件来确认终止原因。当 reason 字段为 RssThresholdKiller 时,说明本次终止就是因为 RSS 内存泄漏。

// json
// APP_KILLED 事件回调示例
HiAppEvent eventInfo={
  "domain": "OS",
  "name": "APP_KILLED",
  "eventType": 2,
  "params": {
    "app_running_unique_id": "616930575450354120",
    "bundle_version": "1.0.1",
    "foreground": true,
    "reason": "RssThresholdKiller", // ← 关键:RSS 内存泄漏导致终止
    "time": 1777877700534
  }
}

       💡 提示:如果应用在同一个生命周期内触发多次故障上报,那么这几次故障事件会持有相同的 app_running_unique_id,开发者可以根据该 ID 对应用发生的多个故障进行关联分析。

五、日志规格:你的"破案工具箱"

       系统检测到 RSS 内存泄漏后,会自动抓取维测日志并发送到应用沙箱。开发者可以从故障事件的 external_log 字段中提取日志路径,然后使用以下三类日志进行分析:

5.1 轻量化 smaps 日志

       smaps 日志记录了进程 RSS 内存的详细分布情况,是分析内存泄漏的"第一手资料"。通过它,你可以看到每一块内存的大小、类别和驻留状态。

5.2 轻量化 NMD 维测日志

       NMD 日志专门用于分析 NativeHeap 内存的详细分布。重点关注 size 和 allocated 两列:

// log
// LOGGER_MEMCHECK_SAMPLE_NMD_INFO 日志示例
// size: 用户申请的内存经过对齐后的大小(向下取整)
// allocated: 该 size 对应的 NativeHeap 内存总大小
// nmalloc: 该 size 的 malloc 总次数
// ndalloc: 该 size 的 free 总次数
size allocated nmalloc ndalloc
8 17384 511848 509675
16 129376 338438 330352
32 1138816 1026155 990567
48 3161808 1322095 1256224
64 1869376 908151 878942
......

       分析技巧:优先查看 allocated 较大的 size 行,因为那代表该尺寸申请的内存总量最大,最可能是泄漏的"重灾区"。

5.3 内存栈日志

       内存栈日志记录了抓栈期间进程申请的 RSS 内存的调用栈,是定位到具体代码行的"终极武器"。

5.4 内存栈日志获取方法

       运维态维测默认只包含轻量 smaps,如需进一步定位到代码行,有以下三种方式:

          • 通过订阅应用灰度采集,在运维态订阅内存调用栈日志

          • 应用自行监听 RSS 水位线,在合理时机调用 OH_HiDebug_StartProfiler() 主动采集内存调用栈日志

          • 通过用户描述或流水日志推测故障复现路径,在 DevEco Studio 中使用 Profiler 工具的 Allocation 功能抓取内存调用栈日志

💡 提示:

       1. 应用内存栈受到内存泄漏速度的影响较大。对于快速泄漏问题,推荐在订阅资源泄漏事件的同时,使用 setEventConfig() 方法补充订阅应用页面切换信息,以此分析泄漏场景,并在开发态对泄漏问题进行复现和定位。

       2. 内存调用栈采集时,对应用性能存在一定影响,影响大小和应用内存申请频率和业务场景强相关,并且需要能命中泄漏时机,命中率无法预测。


(上篇完。在上篇中,我们了解了 RSS 内存泄漏的基本概念、故障感知方式以及日志规格。下篇将聚焦运维态与开发态的分析方法,带你从日志一步步定位到具体代码行,敬请继续阅读。)


      🔗 官网开发者学堂视频:华为开发者学堂

     🔗 社区DFX专题文章:  华为开发者问答 | 华为开发者联盟

【扫码加入 HarmonyOS DFX 技术交流群】

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐