内存限制器

Android 17 及更高版本支持内存限制器,这是一项系统服务,用于使用 Linux cgroup v2 监控和限制应用进程的内存用量。内存限制器可防止单个应用消耗过多的系统内存,从而降低系统范围内的内存压力,并防止激进地终止关键进程(内存不足 (OOM) 终止)。

机制

内存限制器与 Activity Manager Service (AMS) 集成,以跟踪进程生命周期事件和状态变化。内存限制器使用 Linux 内核 cgroup v2 文件系统强制执行内存限制。

如需使用内存限制器,设备内核必须支持 cgroup v2 和 memory 控制器。该服务特别依赖于以下属性:

memory.high
软性限制。超出此限制后,进程会受到限制,并且内核会尝试从中回收内存。
memory.swap.max
限制进程可以使用的交换空间量。

对应用的影响

未超出内存限制的应用不受内存限制器的影响。

当应用超出其 memory.high 限制时,内核会驱逐应用的文件支持内存并交换其匿名内存,以使应用保持在限制范围内。由于驱逐和交换,应用可能会运行得更慢。

在极端情况下,如果应用继续分配匿名内存,并且设备耗尽了交换空间,则应用可能无法分配内存,因此很可能会崩溃。

进程监控

默认情况下,内存限制器会监控应用进程 (UID >= 10000)。系统进程通常不受限制,以帮助验证核心系统稳定性。

内存限制器会根据进程的状态分配内存限制:

  • 可见进程 处于可以向用户显示界面的状态。 在显示界面时,进程可能需要使用更大的 RAM 工作集,因此会获得更宽松的内存限制。

  • 不可见进程 处于正在积极执行工作但未绘制界面的状态。它们使用内存来执行此工作,但比显示界面时所需的内存少,因此会获得更严格的限制。

下表将特定进程状态映射到内存限制:

进程状态内存限制
PERSISTENT无限制
PERSISTENT_UI无限制
TOP可见
BOUND_TOP可见
FOREGROUND_SERVICE不显示
BOUND_FOREGROUND_SERVICE不显示
IMPORTANT_FOREGROUND可见
IMPORTANT_BACKGROUND不显示
TRANSIENT_BACKGROUND不显示
BACKUP不显示
SERVICE不显示
RECEIVER不显示
TOP_SLEEPING可见
HEAVY_WEIGHT不显示
HOME不显示
LAST_ACTIVITY不显示
CACHED_ACTIVITY已缓存
CACHED_ACTIVITY_CLIENT已缓存
CACHED_RECENT已缓存
CACHED_EMPTY已缓存

在缓存状态下,进程会被冻结,然后最大限度地回收。

当进程超出其分配的 memory.high 限制时,内存限制器会检测到该事件,并可以触发调试操作,例如捕获内存配置文件或将异常记录到 statsd

配置

使用位于 vendor 分区上的 XML 文件配置内存限制器。通过配置,您可以根据设备的特定内存限制调整绝对内存限制。

  • 文件路径/vendor/etc/memory-limiter-config.xml

  • 默认配置: 如果找不到配置文件,或者该文件不可读或无效,则内存限制器将被停用。

XML 格式

配置文件遵循 memory-limiter-config.xsd 中定义的架构。该文件允许您定义多个限制集;该服务会根据设备的可用 RAM 选择最佳匹配项。所有内存值均以兆比字节 (MiB) 为单位定义。

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <limitSet>
      <!-- Limits for a phone with at least 14G of ram: 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
用于标识配置版本的正整数。此值必须为 1。
minimumRequiredMemTotal
此限制集生效所需的最低可用系统内存。
memVisible
允许可见进程使用的内存限制 (memory.high)。
memNotVisible
允许不可见进程使用的内存限制 (memory.high)。
swapVisible
允许可见进程使用的交换限制 (memory.swap.max)。
swapNotVisible
允许不可见进程使用的交换限制 (memory.swap.max)。

设备内存限制准则

为设备配置内存限制时,请考虑以下准则:

  • 根据硬件功能定制限制: 设备原始设备制造商 (OEM) 可以设置根据其设备硬件功能定制的限制。Android 建议使用以下范围:

    • 可见进程: 至少为物理 RAM 总量的 1/2,最多为物理 RAM 总量的 2/3。
    • 不可见进程: 物理 RAM 总量的 1/4 到 1/3。 OEM 可以根据设备功能和用例做出不同的决定。
  • 没有适用于应用的运行时 API: 从 Android 17 (SDK 37) 开始,应用没有在运行时查询内存限制的 API。OEM 应考虑到这一点,避免设置过低的限制,确保应用在合理的用例中不会达到限制。

  • 通用配置: 这些限制适用于设备上的所有应用进程,包括预安装的应用。没有允许某些应用不受这些限制的许可名单。

修改配置

如需更改系统范围内的限制,请按以下步骤操作:

  1. 修改 /vendor/etc/memory-limiter-config.xml
  2. 重启设备或重启 system_server 以使更改生效。

Shell 命令

您和开发者可以使用 am memory-limiter 命令在运行时与该服务交互,以进行开发和测试:

am memory-limiter <SUB-COMMAND>

状态

status 子命令会报告内存限制器的运行状态:

adb shell am memory-limiter status

输出示例:

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

输出中的关键字段包括:

monitoring
指示限制器是否正在积极监控进程。
visibleMemnotVisibleMem
指示每种状态的计算绝对内存限制。
events
进程超出其限制的次数。
processes
受监控的进程数。

忽略

ignore 子命令会暂时排除 UID 或所有进程,使其不受限制。此操作对于性能测试或允许特定应用超出其限制非常有用。

adb shell am memory-limiter ignore 10087  // Ignore a specific UID
adb shell am memory-limiter ignore all    // Ignore all processes (effectively disables limiting)
adb shell am memory-limiter ignore none   // Resume normal operation

手动

manual 子命令会使用自定义的绝对值(以兆字节 (MB) 为单位)替换特定进程(按进程 ID 或 PID)的计算限制:

adb shell am memory-limiter manual 1234 1024   // Set a 1024 MB limit for PID 1234
adb shell am memory-limiter manual 1234 none // Remove the manual override for PID 1234

手动替换仅适用于进程的生命周期。如果进程重启,它将根据其状态恢复为默认限制。