内存限制器

Android 17 及更高版本包含内存限制器,这是一种系统服务,可使用 Linux cgroup v2 监控和限制应用进程的内存用量。内存限制器可防止单个应用消耗过多的系统内存,从而减轻整体内存压力,并防止关键前台进程和缓存进程因内存不足而被强制终止。

机制

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

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

memory.high
软性限制。超出此限制后,进程会受到限制,并且内核会尝试从该进程主动回收页面。
memory.swap.max
限制进程可以使用的交换空间(例如 ZRAM)的最大容量。

对应用的影响

在内存限制范围内运行的应用不受内存限制器的影响。

当应用超出其 memory.high 限制时,内核会逐出应用的干净文件支持内存,并换出其非活跃匿名内存,以使应用保持在限制范围内。由于这种页面逐出和交换活动,应用可能会遇到暂时的执行限制,但会继续运行。

在极端情况下(例如,在交换空间耗尽后,应用继续分配匿名内存,导致内存泄漏不受控制),应用无法分配内存并会被终止。

Process Monitoring

默认情况下,内存限制器会监控应用进程 (UID >= 10000)。核心系统进程不受此限制,以保持基本的系统稳定性。

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

  • 可见进程:托管用户可见界面(例如当前获得焦点的 activity 或活跃互动)的进程。由于显示界面需要更大的工作集来运行渲染和图形流水线,因此可见进程会获得更宽松的内存限制。

  • 不可见进程:在后台执行工作而不显示界面的进程(例如后台服务或广播接收器)。由于它们不呈现界面组件,因此在更严格的限制下运行。

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

进程状态内存上限
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。

配置

在 Android 17 及更高版本中,内存限制器使用系统分区上的标准平台配置文件进行配置,该文件位于 /system/etc/memory-limiter-config.xml。

XML 格式

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

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM boundaries account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device [14336, 18432): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>18432</maximumAllowedMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device [9216, 14336): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>9216</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>14336</maximumAllowedMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device [6800, 9216): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>9216</maximumAllowedMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device [4800, 6800): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4800</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>6800</maximumAllowedMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device [3200, 4800): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3200</minimumRequiredMemTotal>
      <maximumAllowedMemTotal>4800</maximumAllowedMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
一个正整数,用于标识配置版本。此设置必须是 1。
minimumRequiredMemTotal

应用此限制集所需的最低系统 MemTotal(以 MiB 为单位,包含下限)。该服务会根据内核在 /proc/meminfo 中报告的总可用内存对此进行评估。

了解内存预留和边界:内核 MemTotal 值反映了扣除硬件和固件内存预留后,操作系统可访问的物理 DRAM。预留内存会为 GPU、基带调制解调器、相机 ISP、安全执行环境和虚拟机监控程序等专用硬件组件预留 RAM,通常会消耗 500 MiB 到 2 GiB 以上的物理内存。

由于 MemTotal 低于设备标称的物理 DRAM,因此 minimumRequiredMemTotal 阈值包含这些预留内存的余量。例如,针对 16 GB 设备的配置指定下限为 14336 (14 GiB),而不是 16384 (16 GiB);针对 12 GB 设备的层级指定下限为 9216 (9 GiB)。这可确保每台设备都与其预期层级相匹配,而不管板级 carve-out 变体如何。

maximumAllowedMemTotal

此限制集适用的系统 MemTotal 上限(以 MiB 为单位,不含上限)。如果 MemTotal 大于或等于此上限,则设置的限制不适用。对于内存不低于 18,432 MiB 或低于 3,200 MiB 的设备,内存限制器不会应用限制,因此进程不受约束。

memVisible

应用于可见进程的软内存限制 (memory.high),以 MiB 为单位。

memNotVisible

应用于不可见进程的软内存限制 (memory.high),以 MiB 为单位。

swapVisible

应用于可见进程的交换空间限制 (memory.swap.max),以 MiB 为单位。

swapNotVisible

应用于不可见进程的交换空间限制 (memory.swap.max)(以 MiB 为单位)。

标准平台限制

在 Android 17 及更高版本中,平台使用 [floor, ceiling) 范围间隔在 /system/etc/memory-limiter-config.xml 中指定常见物理 RAM 层级的标准内存和交换限制:

目标物理 RAM MemTotal 范围 [下限,上限)
(minimumRequiredMemTotal,
maximumAllowedMemTotal)
可见内存上限
(memVisible)
不可见内存上限
(memNotVisible)
可见交换限制
(swapVisible)
不可见交换限制
(swapNotVisible)
16 GB [14336, 18432) MiB 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB [9216, 14336) MiB 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB [6800, 9216) MiB 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB [4800, 6800) MiB 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB [3200, 4800) MiB 2,048 MiB (2 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB)

内存限制原则

内存限制器配置是围绕以下平台原则设计的:

  • 生态系统一致性和应用兼容性:设备间一致的内存限制可确保整个 Android 生态系统中的应用性能可预测。应用是根据标准内存预期进行开发和测试的;保持统一的平台限制可避免意外限制或过早终止。

  • 按比例分配资源:平台限制根据物理 RAM 容量进行校准:

    • 可见进程:分配了大约 1/2 到 2/3 的总物理 RAM,以支持活跃的界面、合成和渲染工作负载。
    • 不可见进程:为后台任务分配了大约 1/4 到 1/3 的总物理 RAM。
  • 普遍适用性:限制统一适用于设备上的所有应用进程 (UID >= 10000),包括预安装的系统应用和 OEM 应用。内存限制器不支持通过许可清单来豁免特定应用,从而确保对所有应用进行公平的内存管理。如需在关键交互式守护程序上配置自调优 memory.min 保护下限,并在各个系统应用和服务上配置更严格、根据工作负载校准的 memory.high 上限,请参阅系统内存预算。

  • 缺少运行时查询 API:在 Android 17 及更高版本中,应用无法在运行时以编程方式查询其分配的内存上限。标准平台限制在定义时留有充足的余量,以便行为良好的应用在正常使用期间不受限制地运行。

  • 内核回收和交换动态:当应用进程接近其 memory.high 限制时,Linux 内核会专门针对该进程 cgroup 进行内存回收。这包括丢弃非活跃的文件支持页面,以及将冷匿名页面交换到 ZRAM,从而在不施加全局内存压力的情况下保持系统流畅性。

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
指示内存限制器是否正在主动监控进程。
visibleMem和notVisibleMem
当前针对每种可见性状态强制执行的计算出的绝对内存限制 (memory.high)。
visibleSwap和notVisibleSwap
当前针对每种公开范围状态强制执行的计算出的绝对交换限制 (memory.swap.max)。
events
进程超出其分配限制的次数。
processes
当前受监控的进程数。

ignore

ignore 子命令会暂时将特定 UID 或所有进程从内存限制中排除。这对于性能基准测试、压力测试或诊断内存行为非常有用:

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

手动

manual 子命令会使用自定义绝对值(以字节为单位)覆盖特定进程(按进程 ID [PID] 指定)的计算限制。该值必须是整数,但可以包含 MB 后缀(表示 MiB)或 GB 后缀(表示 GiB):

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

手动替换仅适用于该特定进程实例的生命周期。 如果进程重启,则会根据其状态恢复为标准限制。

手动替换值不能超过物理系统限制。