3. Android进程与内存模型
Android 的内存回收并不是“谁占得多就杀谁”,而是围绕“进程对用户有多重要”建立的一套分级模型。理解这套模型,是后续理解 LMKD(Low Memory Killer Daemon)调优的前提。本节从进程优先级出发,串联 oom_adj / oom_score_adj、进程生命周期,以及内存分组与 cgroup 基础,帮助你建立一张完整的“内存压力下的进程处置地图”。
一、核心原理:优先级决定“谁先被牺牲”
Android 在 Linux 进程模型之上,叠加了一套面向用户体验的重要性分级。系统会根据进程当前承载的组件(前台 Activity、可见界面、后台服务、缓存等)动态调整其优先级。当内存紧张时,内核与 LMKD 依据这个优先级决定回收顺序:越不重要的进程越先被回收,越接近用户当前操作的进程越晚被回收。
这里有两个容易混淆的参数:
- oom_adj:早期 Android 使用的旧接口,取值范围约为 -17 到 15,数值越小越重要。它由框架层(ActivityManagerService)计算并写入。
- oom_score_adj:Linux 内核主线采用的接口,取值范围为 -1000 到 1000,数值越小越重要。现代 Android 已统一使用它,oom_adj 更多作为历史概念存在。
内核的 OOM 评分机制会把进程实际内存占用与 oom_score_adj 结合,得到一个综合分数。分数越高,越容易被选中回收。因此 oom_score_adj 本质上是“人为施加的偏好权重”,而不是内存大小的直接度量。
二、进程生命周期与优先级迁移
一个 Android 应用进程会随着用户操作在多个状态间迁移,其 oom_score_adj 也随之变化。典型路径如下:
- 前台进程(Foreground):用户正在交互,adj 最低,几乎不会被回收。
- 可见进程(Visible):界面可见但不在前台,adj 较低。
- 前台服务进程(Foreground Service):通过通知等方式声明正在执行重要任务,adj 中等偏低。
- 后台进程(Background):用户已离开,adj 升高,进入可回收候选。
- 缓存进程(Cached):仅保留以便快速重启,adj 最高,是内存回收的首选目标。
- 空进程(Empty):无任何活跃组件,最容易被回收。
当内存压力上升时,LMKD 会从 adj 最高的层级开始逐级回收,直到压力缓解。这个过程是“分级递进”的,而不是一次性清空所有后台进程。
三、内存分组与 cgroup 基础
仅有优先级还不够,系统还需要对进程进行“分组管理”,以便统一限制和统计内存。这正是 cgroup(control group)的作用。Android 使用 cgroup 将进程划分到不同的内存组中,常见的分组包括:
- foreground 组:前台与可见进程,内存限制较宽松。
- background 组:后台进程,限制更严格。
- system 组:系统关键进程,通常受到保护。
通过 cgroup,系统可以对不同组设置内存上限、统计内存使用量,并在组内触发回收。LMKD 会结合 cgroup 的内存压力信息与进程的 oom_score_adj,做出更精细的回收决策。
# 查看进程的 oom_score_adj(示例)
adb shell cat /proc/<pid>/oom_score_adj
# 查看 cgroup 挂载与分组信息
adb shell cat /proc/<pid>/cgroup
# 查看 LMKD 相关属性
adb shell getprop | grep lmkd
四、案例场景:一次后台应用被回收的完整链路
假设用户正在使用浏览器,此时音乐应用退到后台继续播放。系统会经历如下过程:
- 音乐应用从前台变为后台,ActivityManagerService 将其 oom_score_adj 从较低值调高。
- 该进程被归入 background cgroup,内存统计与限制随之生效。
- 用户继续打开大型游戏,系统内存压力上升。
- LMKD 检测到内存低于阈值,从 adj 最高的缓存进程开始回收。
- 若压力持续,音乐应用(后台但仍在播放)可能被回收,除非它已通过前台服务提升优先级。
五、常见误区与复盘
| 误区 | 正确理解 |
|---|---|
| oom_score_adj 越低,进程占用内存越少 | 它只表示回收倾向,与内存占用无直接关系 |
| 手动改 adj 就能永久保活 | 框架层会动态重算,且系统策略优先 |
| 缓存进程没有价值,可以随意杀 | 缓存进程用于加速重启,过度回收会拖慢体验 |
| cgroup 只用于限制 CPU | cgroup 同样用于内存分组、统计与回收 |
复盘时建议重点关注:进程当前处于哪个优先级层级、oom_score_adj 是否被异常修改、所属 cgroup 分组是否合理,以及 LMKD 的回收阈值是否与设备内存规格匹配。只有把“优先级—adj—cgroup—LMKD”这条链路打通,后续的调优才有稳固的落脚点。