内存限制器

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)。UID 低于 10000 的进程不会受到平台限制,默认设备端沙盒化推理服务 (config_defaultOnDeviceSandboxedInferenceService) 的软件包也不会受到限制。系统应用和服务仍然可以声明更严格的内存预算,如系统内存预算中所述。

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

  • 可见进程:托管用户可见界面(例如当前获得焦点的 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已缓存

PERSISTENT 和 PERSISTENT_UI 进程不会收到平台限制。如果其中一个进程声明了内存预算,则其前台预算适用,如内存预算存储桶中所述。

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

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

内存预算存储桶

在支持内存预算的设备上,应用和系统服务可以声明比平台限制更严格的预算。应用会在其清单中声明预算,如设置应用内存预算中所述。对于系统应用和服务,请参阅系统内存预算。预算会设置 memory.high,并且不能超过同一进程状态的平台限制。

当进程有预算时,内存限制器会将其进程状态映射到四个存储桶之一。清单 android:state 值 foreground、perceptible 和 background 会为匹配的存储桶选择预算。缓存存储分区中的进程没有有效预算。

进程状态预算范围
PERSISTENT前景
PERSISTENT_UI前景
TOP前景
BOUND_TOP前景
TOP_SLEEPING前景
FOREGROUND_SERVICE可感知
BOUND_FOREGROUND_SERVICE可感知
IMPORTANT_FOREGROUND可感知
IMPORTANT_BACKGROUND可感知
TRANSIENT_BACKGROUND背景
BACKUP背景
SERVICE背景
RECEIVER背景
HEAVY_WEIGHT背景
HOME已缓存
LAST_ACTIVITY已缓存
CACHED_ACTIVITY已缓存
CACHED_ACTIVITY_CLIENT已缓存
CACHED_RECENT已缓存
CACHED_EMPTY已缓存

配置

在 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

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

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