Android 17 及更高版本支持内存管理守护程序 (mmd),这是一个系统守护程序,用于处理守护程序配置、可调参数以及正在进行的交换或 ZRAM 维护任务。
背景
在引入 mmd 之前,Android 的 ZRAM 配置非常分散,可自定义程度有限。mmd 通过集中管理 ZRAM 来解决此问题,从而实现更复杂的配置逻辑,并简化新功能和架构改进的添加。
mmd 还在基于 Java 的 system_server 进程与内核级交换或内存管理之间建立了清晰的职责分离。
架构和 ZRAM 管理
在启动完成时(即当 sys.boot_completed=1 时),mmd_setup 会尝试使用指定的参数配置 ZRAM。ZRAM 设置完成后,系统会启用 mmd 服务,该服务负责处理持续维护任务。
在 mmd 项目中,维护操作通过以下方式从 system_server 发起:使用 IMmd 接口向 mmd 发送 Binder 请求。mmd 根据其自身的内部政策引擎处理执行 ZRAM 回写、重新压缩和按进程回写的维护任务。ActivityManagerService 中的调度和 ZRAM 维护政策均可使用系统属性进行配置。
系统服务器集成 (system_server)
基于 Java 的 system_server 进程会确定何时调用 mmd。该流程将全局维护扫描与针对每个应用的内存优化分开。
正常的后期处理维护
全局 ZRAM 维护由 ActivityManagerService 使用 com.android.server.memory.ZramMaintenance 驱动。

图 1. ZRAM 维护调度流程。
- 调度引擎:
ZramMaintenance向 Android 的JobScheduler注册一个周期性后台作业。 - 作业限制:为防止前台界面卡顿或 CPU 争用,作业明确配置了
setRequiresDeviceIdle(true)和setRequiresBatteryNotLow(true)。 - Binder 触发:当调度器触发
onStartJob()时,system_server会调用mmd.doZramMaintenanceAsync()。这是一个单向异步 Binder 调用;system_server不会阻塞,等待维护扫描完成。mmd将此任务排入后台工作器线程的队列,以按顺序执行重新压缩和回写。
按进程回写
ActivityManagerService 使用 com.android.server.am.CachedAppOptimizer 管理有针对性的按进程内存逐出。

图 2. 每个进程的 mmd 回写流程。
当进程转换到后台缓存状态时,ActivityManager 会执行内存压缩。如果进程的低内存终止对用户可见(即进程托管 Activity),并且 ZRAM 的进程回写会将进程的内存占用量降至接近于零,则系统会执行以下步骤:
- 压缩完成后,
CachedAppOptimizer会向其内部压缩处理程序发布延迟消息 (ZRAM_WRITEBACK_MSG)(延迟时间为mZramWritebackWaitSeconds)。 - 当延迟时间到期时,ActivityManager 会打开一个安全进程文件描述符
pidfd。 - 系统服务器调用
mmd.asyncWritebackProcessZramMemory(pfd, callback)。 mmd执行每个进程的回写 ioctl,并使用IMmdProcessWritebackCallback进行报告。如果成功,ActivityManager 会标记进程记录 (setIsZramWrittenBack(app, true)) 以提升进程的oom_score_adj,并将指标记录到FrameworkStatsLog.ZRAM_WRITEBACK_EVENT。
按进程预提取
当用户重新启动之前缓存的应用(因 UNFREEZE_REASON_ACTIVITY 而解冻)时,ActivityManager 会最大限度地减少因来自后备存储空间的主要页面错误而导致的应用启动延迟:
CachedAppOptimizer拦截取消冻结事件并调用prefetchZram(app)。- 系统服务器使用
mmd.asyncPrefetchProcessZramMemory(pfd)通过 Binder 调度应用的pidfd。mmd会发出ZRAM_ANDROID_IOC_PROCESS_PREFETCHioctl,指示内核在应用的主界面线程初始化时异步预提取换出的页面并将其放回 RAM 中。
维护和后处理任务概览
本部分介绍了 mmd 为优化交换空间和系统内存而运行的后台维护操作和后处理任务。
以 mmd 为单位的维护
在 mmd 中,维护是指计划的后台维护扫描,用于优化交换空间和物理内存利用率,而不会影响活跃用户的前台性能。维护操作不是持续同步扫描(这会导致严重的 CPU 唤醒和界面卡顿),而是异步驱动的:
system_server会定期通过 Binder 触发doZramMaintenanceAsync()。mmd将请求放入后台工作队列LowPrioWorkItem::ZramMaintenance。mmd中有一个工作器线程,用于同时管理高优先级队列和低优先级队列。高优先级工作项(例如按进程预提取)会优先处理,并且可以抢占低优先级工作项。维护和按进程回写操作作为低优先级工作项运行。弹出时,工作器线程会按顺序执行两项主要维护操作:ZRAM 重新压缩:扫描现有交换页面,并使用更高比率的辅助压缩算法(例如
zstd)重新压缩空闲页面。ZRAM 回写:扫描空闲页面,并将其从 RAM 完全逐出到
/data上文件中的后备闪存存储环路设备。
ZRAM 中的后处理任务
在 Linux 内核 ZRAM 模块和 mmd 架构中,后处理任务是指在内存页已被内核的标准回收路径(kswapd 或内存整理)换出后应用于内存页的异步转换。
当页面最初被换出时,系统会优先考虑速度:它会使用快速的主压缩算法(例如 lz4),并将压缩后的页面存储在 RAM 中。不过,随着时间的推移,许多交换的页面会变得冷或空闲,例如,数小时未恢复的后台缓存应用。将冷页面留在快速、轻度压缩的 ZRAM 中效率低下。
后处理流水线
mmd 实现了一个多阶段后处理生命周期,用于优化这些网页:

图 3. mmd 页面生命周期。
阶段 1:初始换出(快速压缩):首先通过 kswapd 或应用压缩回收内存。通常,首次回收使用
lz4等快速压缩算法执行,内容存储在 RAM 中。第 2 阶段:空闲标记(老化和跟踪):
mmd空闲跟踪会访问内核内存跟踪 (CONFIG_ZRAM_TRACK_ENTRY_ACTIME) 或使用其软件空闲标记来跟踪页面保持未触碰状态的时间。第 3 阶段:后处理 1 - 重新压缩(内存回收):达到重新压缩空闲时间(
min_idle_seconds至max_idle_seconds)的页面会进行重新压缩。mmd会写入/sys/block/zram0/recompress,以指示内核解压缩lz4页面并使用zstd重新压缩。这样可以回收物理 RAM,而不会导致闪存写入磨损。第 4 阶段:后处理 2 - 回写(逐出到闪存):如果内存压力持续存在,并且页面达到回写空闲时间(通常为 20 小时或更长时间),
mmd会触发回写。mmd写入/sys/block/zram0/idle和/sys/block/zram0/writeback,以将压缩页面完全从 RAM 中逐出到后备闪存。
ZRAM 设置配置
mmd 会加载并处理以下 ZRAM 设置属性:
| 属性 | 使用 | 默认 |
|---|---|---|
mmd.zram.enabled |
mmd ZRAM 设置是否已启用。 |
false |
mmd.zram.num_devices |
要配置的 ZRAM 设备数量。对于数字 N,必须存在设备 zram0 到 zram<N-1>,系统才会设置 sys.boot_completed=1。
可以针对每个设备配置每 ZRAM 设备列表中的属性。
|
1 |
mmd.zram.device_priority |
调用 swapon 时要传递的优先级值。 |
未设置 |
mmd.zram.comp_algorithm |
ZRAM 压缩算法。如果未指定,则使用内核默认压缩算法。 | 未设置 |
mmd.zram.size |
ZRAM 设备大小(以字节为单位),或设备 RAM 大小的百分比,例如 75%。
|
50% |
mmd.zram.writeback.enabled |
是否启用 ZRAM 回写。 | false |
mmd.zram.writeback.device_size |
回写设备的大小(以字节为单位)或数据分区的大小(以百分比表示)。实际设备大小可根据数据分区上的可用空间进行调整。 | 1073741824(1 GiB) |
mmd.zram.writeback.min_free_space_mib |
设置回写设备后需要提供的最小可用空间(以 MiB 为单位)。 | 1536 (1.5 GiB) |
mmd.zram.writeback.use_nr_tags_prop |
当 true 时,使用 mmd.zram.writeback.nr_tags 中的值来配置支持 ZRAM 回写的环回设备的队列深度。这是一种权宜措施,用于解决以下情况:无法配置供应商 SELinux 政策以允许 mmd 直接读取支持 /data 的块设备的 nr_tags。
|
false |
mmd.zram.writeback.nr_tags |
请参阅 mmd.zram.writeback.use_nr_tags_prop。 |
未设置 |
mmd.zram.recompression.enabled |
是否启用 ZRAM 重新压缩功能。 | false |
mmd.zram.recompression.algorithm |
辅助 ZRAM 重新压缩算法。 | zstd |
每个 ZRAM 设备的属性
当 mmd.zram.num_devices 大于 1 时,可以选择按 ZRAM 设备配置特定属性,方法是将该属性设置为包含正好 mmd.zram.num_devices 个元素的以英文逗号分隔的值。这些属性包括:
mmd.zram.sizemmd.zram.comp_algorithmmmd.zram.device_prioritymmd.zram.recompression.enabledmmd.zram.recompression.huge_idle.enabledmmd.zram.recompression.idle.enabledmmd.zram.recompression.huge.enabledmmd.zram.recompression.threshold_bytesmmd.zram.recompression.algorithmmmd.zram.writeback.device_sizemmd.zram.writeback.huge_idle.enabledmmd.zram.writeback.idle.enabledmmd.zram.writeback.huge.enabled
弃用现有 ZRAM 设置
虽然 Android 中仍提供 swapon_all 来设置 ZRAM 和基于磁盘的交换空间,但 mmd 是 ZRAM 管理的首选方法,可实现更轻松的配置和 ZRAM 重新压缩等高级功能。
当 mmd ZRAM 设置由 mmd.zram.enabled 启用时:
swapon_all实现中的 ZRAM 设置变为空操作。- 系统会忽略叠加层
config.xml文件中的现有 ZRAM 配置(例如config_zramWriteback)和ro.zram.*回写系统属性。
ZRAM 维护可调参数
ZRAM 维护应开箱即用,您可以使用本部分中的系统属性进一步微调。
ZRAM 维护调度
这些属性控制 system_server 如何以及何时安排 ZRAM 维护任务。
| 属性 | 使用 | 默认 |
|---|---|---|
mm.zram.maintenance.first_delay_seconds |
首次启动 ZRAM 维护之前的延迟时间。 | 3600(1 小时) |
mm.zram.maintenance.periodic_delay_seconds |
后续 ZRAM 维护调度之间的延迟。 | 3600(1 小时) |
mm.zram.maintenance.require_device_idle |
是否仅在设备处于空闲状态时启动 ZRAM 维护。 | true |
mm.zram.maintenance.require_battery_not_low |
是否需要在启动 ZRAM 维护之前要求电池电量不低。 | true |
ZRAM 回写政策
以下参数用于控制何时将哪种类型的内存写入后备设备:
| 属性 | 使用 | 默认 |
|---|---|---|
mmd.zram.writeback.backoff_seconds |
自上次回写操作以来的退避时间。 | 600(10 分钟) |
mmd.zram.writeback.min_idle_seconds |
与 mmd.zram.writeback.max_idle_seconds 结合使用,可根据内存利用率分数计算出页面符合回写条件的空闲时间。计算出的空闲期限在两个参数之间进行指数插值,以在没有内存压力的情况下最大限度地减少工作量。
|
72000(20 小时) |
mmd.zram.writeback.max_idle_seconds |
用于根据内存利用率动态计算空闲页面年龄的最大秒数。 | 90000(25 小时) |
mmd.zram.writeback.huge.enabled |
是否启用 HUGE 页面回写。 |
false |
mmd.zram.writeback.idle.enabled |
是否启用 IDLE 页面回写。 |
true |
mmd.zram.writeback.huge_idle.enabled |
是否启用 HUGE_IDLE 页面回写。 |
true |
mmd.zram.writeback.min_bytes |
一次空闲写回中要写回的最小字节数。 | 5242880(5 MiB) |
mmd.zram.writeback.max_bytes |
一次空闲写回中要写回的最大字节数。 | 314572800(300 MiB) |
mmd.zram.writeback.max_bytes_per_day |
24 小时内要写回的最大字节数。 | 25769803776(24 GiB) |
mmd.zram.writeback.limit.enabled |
是否启用每日回写预算限额结算。 | true |
ZRAM 重新压缩政策
以下参数用于控制何时以及重新压缩哪种类型的内存:
| 属性 | 使用 | 默认 |
|---|---|---|
mmd.zram.recompression.backoff_seconds |
自上次重新压缩以来的退避时间。 | 1800(30 分钟) |
mmd.zram.recompression.min_idle_seconds |
与 mmd.zram.recompression.max_idle_seconds 结合使用,可根据内存利用率分数计算页面符合重新压缩条件的空闲时间。计算出的空闲期限在两个形参之间进行指数插值,以在不承受内存压力的情况下最大限度地减少工作。
|
7200(2 小时) |
mmd.zram.recompression.max_idle_seconds |
用于动态计算空闲网页年龄的最大秒数。 | 14400(4 小时) |
mmd.zram.recompression.threshold_bytes |
考虑重新压缩的 ZRAM 页面的最小大小(以字节为单位)。 | 1024(1 KiB) |
mmd.zram.recompression.huge.enabled |
是否启用 HUGE 页面重新压缩。 |
true |
mmd.zram.recompression.idle.enabled |
是否启用 IDLE 页面重新压缩。 |
true |
mmd.zram.recompression.huge_idle.enabled |
是否启用 HUGE_IDLE 页面重新压缩。 |
true |
ZRAM 空闲页面跟踪
mmd ZRAM 维护会根据 ZRAM 页面上次访问时间的长短,将 ZRAM 页面标记为空闲。此功能需要启用 CONFIG_ZRAM_TRACK_ENTRY_ACTIME 或 CONFIG_ZRAM_MEMORY_TRACKING 内核配置。在 GKI 内核 6.18 及更高版本中,CONFIG_ZRAM_TRACK_ENTRY_ACTIME 默认处于启用状态。在较早的内核上,它具有内存开销,并且默认情况下未启用。
如果未启用内核配置,mmd ZRAM 维护会回退到软件替代逻辑来跟踪空闲 ZRAM 页面:
在
mmd启动时,将所有 ZRAM 页面标记为空闲。跳过下一次 ZRAM 维护,直到所需的退避时间过去。
ZRAM 回写或重新压缩空闲页面。如果由于回写限制而导致仍有空闲页面,
mmd会在下一次维护时继续回写页面,而不会将新页面标记为空闲(跳过第 4 步)。如果所有空闲页面都已写回,则再次将所有 ZRAM 页面标记为空闲,然后返回到第 2 步。如果 ZRAM 回写处于停用状态,则在重新压缩空闲时长过后发生 ZRAM 重新压缩时,
mmd会将所有 ZRAM 页面标记为空闲。
问题排查和验证指南
请按照以下验证步骤和问题排查程序来验证和诊断 mmd 和 ZRAM 操作。
验证 ZRAM 设置
如需验证 mmd 在启动期间是否成功配置了 ZRAM,请执行以下操作:
检查有效压缩算法和磁盘大小:
cat /sys/block/zram0/comp_algorithm cat /sys/block/zram0/disksize验证
mmd系统属性和正在运行的服务状态:getprop | grep mmd.zram dumpsys -l | grep mmd
验证 ZRAM 维护和回写
验证 ZRAM 回写和重新压缩维护任务是否正常运行:
检查后备块设备状态:
cat /sys/block/zram0/bd_stat通过监控
/sys/block/zram0/mm_stat检查重新压缩效率。压缩数据大小的变化应在维护周期后显示。
验证每个进程的回写
以下内容可用于验证按进程回写是否正常运行:
- 检查
adb logcat -s mmd是否包含成功回写的日志或失败诊断信息。
常见问题和诊断
以下是用户可能会遇到的常见错误情况:
WritebackDailyLimitExceeded:此错误表示已达到mmd.zram.writeback.max_bytes_per_day配额。发生这种情况时,mmd会暂停空闲回写,直到 24 小时的滚动窗口推进。Process prefetch or writeback failed:当 ioctl 失败时,可以在 logcat 中观察到此错误。常见原因包括:EBADF或ESRCH:目标进程在mmd将pidfd调度到内核之前结束。ENOSPC:后备存储分区已满,或环回设备队列已耗尽。
- ZRAM 未设置:如果
mmd在启动时未能配置 ZRAM,可能是因为旧版swapon_all或供应商 init 脚本在mmd执行之前锁定了/dev/block/zram0。