Android 17 及更高版本包含内存限制器,这是一种系统服务,可使用 Linux cgroup v2 监控和限制应用进程的内存用量。内存限制器可防止单个应用消耗过多的系统内存,从而减轻整体内存压力,并防止关键前台进程和缓存进程因内存不足而被强制终止。
机制
内存限制器与 Activity Manager Service (AMS) 集成,可跟踪进程生命周期事件和状态变化。内存限制器使用 Linux 内核 cgroup v2 层次结构强制执行内存限制。
为了支持内存限制器,设备内核必须启用 cgroup v2 和 memory 控制器。该服务特别依赖于以下属性:
memory.high- 应用于软件包 UID cgroup 的软回收阈值,或在每个进程的内存预算处于活跃状态时应用于进程 PID cgroup 的软回收阈值。默认为平台内存限制的 90%,除非受到内存预算的限制。如果超出此限制,内核会限制分配并从该 cgroup 回收页面。
memory.max- 在平台内存限制的 100% 处应用于软件包 UID cgroup 的硬内存上限。如果回收无法将内存用量保持在
memory.max以下,内核会终止内存不足的进程。 memory.swap.max- 限制每个进程 PID cgroup 可以使用的最大交换空间(例如 ZRAM)。
Cgroup 和内核要求
搭载 Android 17(API 级别 37)及更高版本的设备必须满足以下 cgroup v2 和内核要求,才能实现平台限制,以及 API 级别 37.2 及更高级别中的清单预算和 MemoryBudgetManager:
按进程激活 cgroup:在
/system/etc/cgroups.json中将"NeedsActivation": true保留在 cgroup v2memory控制器上,以便早期init在创建/sys/fs/cgroup/apps/uid_<UID>/pid_<PID>和/sys/fs/cgroup/system/uid_<UID>/pid_<PID>之前激活+memory。请勿停用"NeedsActivation"、设置ro.config.per_app_memcg=false或添加停用每个 UID 或每个进程的内存 cgroup 的供应商cgroups.json替换。如果/vendor/etc/task_profiles.json替换了内存控制器属性(例如MemLimit或MemSoftLimit),请使"File"保持非空状态,并添加 cgroup v2 文件名的"FileV2"映射。Cgroup 文件权限:
/sys/fs/cgroup/apps和/sys/fs/cgroup/system下的父级uid_<UID>以及每个进程的pid_<PID>控制文件memory.high、memory.max和memory.swap.max必须可由 UID 或 GID 1000 下的system_server写入,以便在没有EACCES错误的情况下应用 UID 限制、进程预算和交换限制。每个 UID 和每个进程的memory.current、memory.events、memory.events.local、memory.stat和memory.high文件,以及每个 UID 的memory.max和memory.swap.max,都必须可由所属应用 UID 读取,以便MemoryBudgetManager查询和超预算监听器在进程内运行。本地 PSI 隔离:通过添加 GKI 本地 memcg PSI 更改并在任何
android_vh_mem_cgroup_handle_over_high供应商钩子中保留record_psi=false,将每个 cgroup 的memory.high回收失速与全局内存压力失速信息 (PSI) 隔离开来。如果没有 PSI 隔离,局部memory.high限制会增加全局内存 PSI,并可能在系统级内存没有压力时触发不必要的lmkd进程终止。
对应用的影响
在内存限制范围内运行的应用不受内存限制器的影响。
当应用超出其 memory.high 限制时,内核会逐出应用的干净文件支持内存,并换出其非活跃匿名内存,以使应用保持在限制范围内。由于这种页面逐出和交换活动,应用可能会遇到暂时的执行限制,但会继续运行。
在极端情况下,例如失控的内存泄漏(应用在超过 memory.max 或交换空间耗尽后继续分配内存),应用无法分配内存并终止。
Process Monitoring
内存限制器会将本页面上的平台限制应用于应用进程 (UID >= 10000)。UID 低于 10000 的进程不会受到平台限制,默认设备端沙盒化推理服务 (config_defaultOnDeviceSandboxedInferenceService) 的软件包也不会受到限制。系统应用和服务仍然可以声明更严格的内存预算,如系统内存预算中所述。
内存限制器会根据进程的可见性状态分配内存限制:
可见进程:托管用户可见界面的进程,例如获得焦点的 activity 或活跃互动。由于显示界面需要更大的工作集来运行渲染和图形流水线,因此可见进程会获得更宽松的内存限制。
不可见进程:在后台执行工作而不显示界面的进程,例如后台服务或广播接收器。由于它们不渲染界面组件,因此在更严格的限制下运行。
下表将特定进程状态映射到平台内存限制分类:
| 进程状态 | 内存上限 |
|---|---|
PERSISTENT |
无限制 |
PERSISTENT_UI |
无限制 |
TOP |
visible |
BOUND_TOP |
visible |
FOREGROUND_SERVICE |
not-visible |
BOUND_FOREGROUND_SERVICE |
not-visible |
IMPORTANT_FOREGROUND |
visible |
IMPORTANT_BACKGROUND |
not-visible |
TRANSIENT_BACKGROUND |
not-visible |
BACKUP |
not-visible |
SERVICE |
not-visible |
RECEIVER |
not-visible |
TOP_SLEEPING |
visible |
HEAVY_WEIGHT |
not-visible |
HOME |
not-visible |
LAST_ACTIVITY |
not-visible |
CACHED_ACTIVITY |
not-visible |
CACHED_ACTIVITY_CLIENT |
not-visible |
CACHED_RECENT |
not-visible |
CACHED_EMPTY |
not-visible |
PERSISTENT 和 PERSISTENT_UI 进程不会收到平台限制。如果其中一个进程声明了内存预算,则其前台预算适用,如内存预算存储桶中所述。
在缓存状态下,进程会保留 not-visible 平台限制,同时进行缓存应用冻结和压缩,即使未发生冻结或最大程度的回收,也能使进程保持在限制范围内。
当进程超出其分配的 memory.high 限制时,内存限制器会检测到该事件,并可触发诊断操作,例如捕获内存配置文件或将异常记录到 statsd。
内存预算存储桶
在 API 级别 37.2 及更高级别中,应用和系统服务可以声明比平台限制更严格的内存预算,也可以通过 MemoryBudgetManager 设置更严格的运行时预算。应用会按照设置应用内存预算中的说明配置预算。对于系统应用和服务,请参阅系统内存预算。预算会设置 memory.high,并且不能超过同一进程状态的平台限制,而运行时预算不能超过适用的清单预算。
当进程声明清单预算时,内存限制器会将其进程状态映射到三个预算存储桶之一,而运行时预算会应用单个上限,直到更新或清除。清单 android:state 值 foreground、perceptible 和 background 用于指定匹配存储桶的预算。处于 CACHED_ACTIVITY 到 CACHED_EMPTY 状态的缓存进程会保留 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大于或等于此上限,则设置的限制不适用。对于MemTotal小于 3,200 MiB 或至少为 18,432 MiB 的设备,内存限制器不会应用默认平台限制,但每个进程的内存 cgroup 仍处于活跃状态(以及 API 级别 37.2 及更高级别中的清单、系统和MemoryBudgetManager运行时预算)。memVisible当软件包具有可见进程时,应用于软件包 UID cgroup 的
memory.max内存硬限制(以 MiB 为单位)。默认的memory.high软回收阈值是此值的 90%,四舍五入到最接近的 MiB。memNotVisible当软件包没有可见进程时,应用于软件包 UID cgroup 的
memory.max内存硬限制(以 MiB 为单位)。默认memory.high软回收阈值是此值的 90%,四舍五入到最接近的 MiB。swapVisible应用于每个可见进程 PID cgroup 的
memory.swap.max交换空间限制(以 MiB 为单位)。swapNotVisible应用于每个不可见进程 PID cgroup 的
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 级别 37.2 及更高级别中,应用可以通过
MemoryBudgetManager查询其内存限制和 cgroup 内存用量,并设置更严格的运行时预算。保留标准平台和清单限制,不进行供应商替换,可使运行时预算行为在不同设备上保持一致。内核回收和交换动态:当应用进程接近其
memory.high限制时,Linux 内核会专门针对该进程 cgroup 进行内存回收。这包括丢弃不活跃的文件支持页面,以及将冷匿名页面交换到 ZRAM,从而在不施加全局内存压力的情况下保持系统流畅性。
Shell 命令
借助 am memory-limiter 命令,开发者和系统集成商可以在运行时与服务进行交互,以进行开发、测试和调试:
am memory-limiter <SUB-COMMAND>状态
status 子命令会报告内存限制器的运行状态、配置的平台限制和活跃监控计数器:
adb shell am memory-limiter status运行 adb shell am memory-limiter status version 会在启用双阈值内存限制器时输出 2,在停用时输出 0。
以下是 8 GB 设备上的 status 输出示例:
Memory limiter
enabled limits=true killing=true ignored=none
visible: memHigh=4608MB memMax=5120MB swapMax=3072MB
not-visible: memHigh=2765MB memMax=3072MB swapMax=3072MB
active=64 uid-started=32 uid-current=28 uid-updated=4
started=72 watched=72 watch-failed=0
limiters=64 limiters-hwm=70 red-poll=0 red-limiter=0
events=0 false-events=0 modify=84
输出中的关键字段包括:
limits和killing- 指明是否针对设备的 RAM 层级配置并强制执行
memory.high和memory.max平台内存限制。 ignored- 被
ignore子命令排除的 UID、all(当限制全局暂停时)或none。 visible和not-visible- 针对每个可见性分类强制执行的默认 UID
memHigh限制(针对memory.high)、UIDmemMax限制(针对memory.max)和每个进程的swapMax限制(针对memory.swap.max)。 activesystem_server中跟踪的进程和 UID 限制器的数量。uid-started、uid-current和uid-updated- 使用清单预算更新的 UID 限制器的累计启动计数、活跃计数和累计替换计数。
watched和watch-failed- 已注册和失败的 cgroup
memory.events监视的累计数量。 limiters和limiters-hwm- 活跃的原生 cgroup 观察器的数量和高水位线。
events和false-events- 分派到
system_server的超限事件数量,以及未超过受监控阈值的memory.events通知数量。
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 子命令会使用自定义整数上限(最高可达目标的配置限制)覆盖计算出的 memory.high、memory.max 和 memory.swap.max 限制。传递 PID(无论是否带有 -p)以定位单个进程,或传递 -u <UID> 以定位软件包 UID 中的所有进程。以字节为单位指定大小,附加 MB 表示 MiB,附加 GB 表示 GiB,或传递 none 以清除替换。为了向后兼容,小于 20 且没有后缀的裸整数会被解释为 MiB:
# Set a 1 GiB limit for PID 1234, with or without -p adb shell am memory-limiter manual 1234 1073741824 adb shell am memory-limiter manual 1234 1024MB adb shell am memory-limiter manual -p 1234 1GB# Set a 1 GiB limit for all processes in UID 10087 adb shell am memory-limiter manual -u 10087 1GB# Remove the manual override for PID 1234 or UID 10087 adb shell am memory-limiter manual 1234 none adb shell am memory-limiter manual -u 10087 none
手动进程替换仅在特定进程实例的生命周期内有效。如果进程重启,则会根据其状态恢复到标准限制。相比之下,manual -u <UID> 替换项会在 UID 保持活跃状态期间一直保留,直到 UID 中的所有进程都退出或您使用 none 清除替换项。
手动替换值不能超过物理系统限制。