9、进程优先级与LRU算法:AMS如何管理进程优先级、adj值、lmk触发机制
进程优先级,说白了就是Android系统给每个App打的“生存分”。
分数高的,系统会优先保它;分数低的,内存不够时第一个被干掉。我刚开始接触这部分时,觉得不就是个优先级嘛,后来才发现,这里面的门道远比想象中复杂。
9.1 什么是adj值?
adj值,全称是oom_adj。它是Linux内核OOM Killer用来判断“先杀谁”的关键指标。
Android在原生Linux基础上,自己定义了一套adj值体系。范围从-17到15,数值越小,进程越重要。
| adj值 | 进程类型 | 典型场景 |
|---|---|---|
| -17 | 系统核心进程 | init、zygote、system_server |
| 0 | 前台进程 | 用户正在交互的Activity |
| 1 | 可见进程 | Activity部分可见,比如被对话框遮挡 |
| 2 | 服务进程 | 正在运行Service |
| 3 | 后台进程 | Activity已不可见 |
| 4 | 空进程 | 没有任何组件在运行 |
| 15 | 缓存进程 | 最容易被杀死的进程 |
嗯,这里要注意:adj值不是一成不变的。AMS会动态调整它。
比如一个App从前台切到后台,adj值会从0变成3。如果它又启动了Service,adj值可能变成2。我见过不少开发者以为进程优先级是固定的,结果在后台服务被频繁杀死时一脸懵。
9.2 AMS如何管理进程优先级?
AMS管理进程优先级,核心逻辑在ProcessList和OomAdjuster这两个类里。
流程大致是这样的:
- AMS收到组件状态变化(比如Activity启动、Service绑定)
- 调用
updateOomAdjLocked()方法 - 遍历所有进程,重新计算每个进程的adj值
- 通过
ProcessList写入/proc/pid/oom_adj - 内核根据adj值决定OOM优先级
我举个例子,你感受一下:
// 伪代码,展示AMS调整adj的核心逻辑
void updateOomAdjLocked() {
for (ProcessRecord app : mProcessList) {
int adj = computeOomAdj(app);
// 写入内核
writeOomAdjToProc(app.pid, adj);
}
}
int computeOomAdj(ProcessRecord app) {
if (app.hasForegroundActivities()) {
return 0; // 前台进程
}
if (app.hasVisibleActivities()) {
return 1; // 可见进程
}
if (app.hasActiveServices()) {
return 2; // 服务进程
}
if (app.hasRecentActivities()) {
return 3; // 后台进程
}
return 4; // 空进程
}
这段代码看着简单,但实际逻辑复杂得多。我记得在Android 8.0之前,AMS每次调整adj都要遍历所有进程,性能开销很大。后来Google优化成了增量更新,只调整状态变化的进程。
9.3 LRU算法在进程管理中的应用
LRU,全称Least Recently Used,最近最少使用算法。
AMS维护了一个LRU列表,用来记录进程的“活跃程度”。
具体来说:
- 当进程被使用时,它会被移到LRU列表的头部
- 当进程长时间未被使用,它会逐渐滑向列表尾部
- 当需要回收内存时,从LRU列表尾部开始杀进程
这个LRU列表和adj值有什么关系呢?
我个人的理解是:adj值决定了进程的“优先级等级”,LRU决定了同一等级内进程的“淘汰顺序”。
举个例子:
你有两个后台进程,adj值都是3。但一个5分钟前被用过,另一个2小时前被用过。LRU列表会把2小时前的那个排在后面,内存不足时先杀它。
核心要点:adj值决定“能不能杀”,LRU决定“先杀谁”。两者配合,构成了Android进程管理的完整体系。
9.4 LMK触发机制
LMK,Low Memory Killer,低内存杀手。它是内核层的一个机制,但触发逻辑和AMS密切相关。
LMK的触发条件很简单:当系统可用内存低于某个阈值时,LMK开始干活。
但这里有个关键点:LMK不是随机杀进程的。它会参考AMS设置的adj值。
流程是这样的:
- AMS通过
writeOomAdjToProc()把adj值写入/proc/pid/oom_adj - LMK监控系统内存,当内存低于阈值时,遍历所有进程
- 找到adj值最大的进程(优先级最低的)
- 发送SIGKILL信号,杀掉该进程
我曾经遇到过一个问题:某个App在后台频繁被杀,用户反馈很强烈。排查后发现,这个App的Service一直在做高耗时的后台任务,导致adj值被AMS调整为2(服务进程)。但问题是,它占用的内存太大,LMK在内存紧张时还是会优先杀它。
避坑指南:如果你的App在后台容易被杀,不要只盯着adj值。检查一下内存占用是否合理。我曾经见过一个App,adj值只有1(可见进程),但因为内存泄漏,占用超过500MB,照样被LMK干掉。
9.5 整体流程可视化
说了这么多,我画张图帮你理清思路:
这张图把整个流程串起来了。从AMS计算adj值,到写入内核,再到LMK触发,每一步都环环相扣。
9.6 实际项目中的经验
我在项目中遇到过一个问题,想分享给你:
有一次,一个视频类App在后台播放音乐时,总是被系统杀死。用户反馈说“听着听着就停了”。
排查过程是这样的:
- 先看adj值:App启动了前台Service,adj值应该是2
- 再看内存:App占用了800MB,因为缓存了多个视频流
- 再看LRU:App虽然adj值低,但LRU列表中排在很后面
最终结论:LMK在内存紧张时,虽然adj值低,但内存占用太大,加上LRU位置靠后,还是被杀了。
注意:adj值不是万能的。如果你的App内存占用过大,即使adj值很低,LMK也可能在极端情况下杀掉它。我建议你在开发时,定期检查App的内存占用,避免无谓的缓存。
好了,关于进程优先级和LMK,今天就聊到这里。记住一句话:adj值是基础,LRU是辅助,LMK是最终执行者。三者配合,构成了Android进程管理的完整体系。
公众号:蓝海资料掘金营,微信deep3321