内存限制器

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

机制

内存限制器与 Activity Manager 服务 (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)。这样可确保每个设备都与其预期层级相符,无论板级划分有何变化。

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 及更高版本中,平台在 /system/etc/memory-limiter-config.xml 中使用 [floor, ceiling) 范围间隔跨常见物理 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 应用。内存限制器不支持用于排除特定应用的许可名单,从而确保所有应用都能公平地使用内存。

  • 缺少运行时查询 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
指示内存限制器是否正在主动监控进程。
visibleMemnotVisibleMem
目前针对每种可见性状态强制执行的计算出的绝对内存限制 (memory.high)。
visibleSwapnotVisibleSwap
当前针对每种公开范围状态强制执行的计算出的绝对交换限制 (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

手动替换仅在相应特定流程实例的生命周期内有效。 如果流程重新开始,则会根据其状态恢复为标准限额。

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