Android 17 及更高版本支持内存限制器,这是一项系统服务,用于使用 Linux cgroup v2 监控和限制应用进程的内存用量。内存限制器可防止单个应用消耗过多的系统内存,从而降低系统范围内的内存压力,并防止激进地终止关键进程(内存不足 (OOM) 终止)。
机制
内存限制器与 Activity Manager Service (AMS) 集成,以跟踪进程生命周期事件和状态变化。内存限制器使用 Linux 内核 cgroup v2 文件系统强制执行内存限制。
如需使用内存限制器,设备内核必须支持 cgroup v2 和 memory 控制器。该服务特别依赖于以下属性:
memory.high- 软性限制。超出此限制后,进程会受到限制,并且内核会尝试从中回收内存。
memory.swap.max- 限制进程可以使用的交换空间量。
对应用的影响
未超出内存限制的应用不受内存限制器的影响。
当应用超出其 memory.high 限制时,内核会驱逐应用的文件支持内存并交换其匿名内存,以使应用保持在限制范围内。由于驱逐和交换,应用可能会运行得更慢。
在极端情况下,如果应用继续分配匿名内存,并且设备耗尽了交换空间,则应用可能无法分配内存,因此很可能会崩溃。
进程监控
默认情况下,内存限制器会监控应用进程 (UID >= 10000)。系统进程通常不受限制,以帮助验证核心系统稳定性。
内存限制器会根据进程的状态分配内存限制:
可见进程 处于可以向用户显示界面的状态。 在显示界面时,进程可能需要使用更大的 RAM 工作集,因此会获得更宽松的内存限制。
不可见进程 处于正在积极执行工作但未绘制界面的状态。它们使用内存来执行此工作,但比显示界面时所需的内存少,因此会获得更严格的限制。
下表将特定进程状态映射到内存限制:
| 进程状态 | 内存限制 |
|---|---|
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。
配置
使用位于 vendor 分区上的 XML 文件配置内存限制器。通过配置,您可以根据设备的特定内存限制调整绝对内存限制。
文件路径:
/vendor/etc/memory-limiter-config.xml默认配置: 如果找不到配置文件,或者该文件不可读或无效,则内存限制器将被停用。
XML 格式
配置文件遵循 memory-limiter-config.xsd 中定义的架构。该文件允许您定义多个限制集;该服务会根据设备的可用 RAM 选择最佳匹配项。所有内存值均以兆比字节 (MiB) 为单位定义。
<MemoryLimiterConfig>
<version>1</version>
<configList>
<limitSet>
<!-- Limits for a phone with at least 14G of ram: 8G/4G/4G/4G -->
<minimumRequiredMemTotal>14336</minimumRequiredMemTotal>
<memVisible>8192</memVisible>
<memNotVisible>4096</memNotVisible>
<swapVisible>4096</swapVisible>
<swapNotVisible>4096</swapNotVisible>
</limitSet>
</configList>
</MemoryLimiterConfig>
version- 用于标识配置版本的正整数。此值必须为 1。
minimumRequiredMemTotal- 此限制集生效所需的最低可用系统内存。
memVisible- 允许可见进程使用的内存限制 (
memory.high)。 memNotVisible- 允许不可见进程使用的内存限制 (
memory.high)。 swapVisible- 允许可见进程使用的交换限制 (
memory.swap.max)。 swapNotVisible- 允许不可见进程使用的交换限制 (
memory.swap.max)。
设备内存限制准则
为设备配置内存限制时,请考虑以下准则:
根据硬件功能定制限制: 设备原始设备制造商 (OEM) 可以设置根据其设备硬件功能定制的限制。Android 建议使用以下范围:
- 可见进程: 至少为物理 RAM 总量的 1/2,最多为物理 RAM 总量的 2/3。
- 不可见进程: 物理 RAM 总量的 1/4 到 1/3。 OEM 可以根据设备功能和用例做出不同的决定。
没有适用于应用的运行时 API: 从 Android 17 (SDK 37) 开始,应用没有在运行时查询内存限制的 API。OEM 应考虑到这一点,避免设置过低的限制,确保应用在合理的用例中不会达到限制。
通用配置: 这些限制适用于设备上的所有应用进程,包括预安装的应用。没有允许某些应用不受这些限制的许可名单。
修改配置
如需更改系统范围内的限制,请按以下步骤操作:
- 修改
/vendor/etc/memory-limiter-config.xml。 - 重启设备或重启
system_server以使更改生效。
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- 指示每种状态的计算绝对内存限制。
events- 进程超出其限制的次数。
processes- 受监控的进程数。
忽略
ignore 子命令会暂时排除 UID 或所有进程,使其不受限制。此操作对于性能测试或允许特定应用超出其限制非常有用。
adb shell am memory-limiter ignore 10087 // Ignore a specific UIDadb shell am memory-limiter ignore all // Ignore all processes (effectively disables limiting)adb shell am memory-limiter ignore none // Resume normal operation
手动
manual 子命令会使用自定义的绝对值(以兆字节 (MB) 为单位)替换特定进程(按进程 ID 或 PID)的计算限制:
adb shell am memory-limiter manual 1234 1024 // Set a 1024 MB limit for PID 1234adb shell am memory-limiter manual 1234 none // Remove the manual override for PID 1234
手动替换仅适用于进程的生命周期。如果进程重启,它将根据其状态恢复为默认限制。