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 设备的配置指定minimumRequiredMemTotal为13312(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- 指示内存限制器是否在主动监控进程。
visibleMem和notVisibleMem- 当前针对每种可见性状态强制执行的计算出的绝对内存限制 (
memory.high)。 visibleSwap和notVisibleSwap- 当前针对每种可见性状态强制执行的计算出的绝对交换限制 (
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
手动替换仅适用于该特定进程实例的生命周期。 如果进程重启,它会根据其状态返回到标准限制。
手动替换不能超出物理系统限制。