内存限制器

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

机制

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

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

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

对应用的影响

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

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

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

进程监控

内存限制器默认监控应用进程 (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 及更高版本中,内存限制器在系统分区上提供标准平台默认配置文件,同时支持在 vendor 分区上进行可选的设备专用替换:

  • 系统默认配置/system/etc/memory-limiter-config.xml
  • 供应商替换(可选)/vendor/etc/memory-limiter-config.xml

内存限制器首先检查 /vendor/etc/memory-limiter-config.xml。 如果存在,则使用供应商配置;否则,它会回退到 /system/etc/memory-limiter-config.xml 中的默认系统配置。

XML 格式

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

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 13GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>13312</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 10GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>10240</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 6.5GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>6656</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 4.5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>4608</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <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 到 1 GiB 以上的物理内存。

由于 MemTotal 低于设备宣传的物理 DRAM, minimumRequiredMemTotal 阈值包含这些预留的余量。 例如,针对 16 GB 设备的配置指定 minimumRequiredMemTotal13312 (13 GiB) 而不是 16384 (16 GiB),而针对 12 GB 设备的层级指定 10240 (10 GiB)。这可确保每个设备都与其预期层级匹配,而无论板级预留的差异如何。

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 中针对常见的物理 RAM 层级指定了标准内存和交换限制:

目标物理 RAM 最低要求
MemTotal
(minimumRequiredMemTotal)
可见内存限制
(memVisible)
不可见内存限制
(memNotVisible)
可见交换限制
(swapVisible)
不可见交换限制
(swapNotVisible)
16 GB 13,312 MiB (13 GiB) 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB 10,240 MiB (10 GiB) 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB 6,656 MiB (6.5 GiB) 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB 4,608 MiB (4.5 GiB) 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB 3,072 MiB (3 GiB) 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 子命令会暂时将特定 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

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

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