lmkd:用户空间的低内存杀手
lmkd:用户空间的低内存杀手
上一章我们有了排序依据。这一章回答:谁来监听内存压力、怎么挑进程、怎么杀,日志里 能看到什么?
现代 Android 的主要低内存回收决策发生在用户空间 lmkd。它接收 PSI 等压力信号,读取 候选进程的优先级和占用,在内核 OOM Killer 兜底前主动释放一部分内存。
lmkd 是用户空间决策者
| 常见误解 | 正确理解 |
|---|---|
| 低内存杀进程的是内核 OOM Killer | 主力是用户空间的 lmkd,内核 OOM 只在极端情况兜底 |
| lmkd 每秒钟轮询内存 | 它通过内核事件(vmpressure/PSI)被动感知压力 |
| 杀进程是随机挑一个 | 按 oom_adj 从高到低挑选,并考虑内存占用与 cgroup 分组 |
核心结论
- lmkd 通过内核压力事件(vmpressure/PSI)被动感知内存紧张,不做高频轮询。
- 候选选择:按 oom_adj 从高到低,叠加内存占用与 cgroup 分组信息。
- 决策结果写入日志(
am_kill等),可用来复盘“谁被杀了、为什么”。 - 内核 OOM 是兜底:lmkd 来不及或压力极端时才会触发。
- 阈值与策略(minfree 档位)来自系统配置,厂商可调。
监听与杀进程主链
逐步解释:
- 压力信号:内核在内存紧张时上报 PSI/vmpressure 事件。
- 监听:lmkd 挂在这些事件上被动唤醒。
- 评估:按 oom_adj、内存占用、cgroup 分组综合挑选目标。
- 执行:向目标进程发送 kill 信号,释放内存。
- 记录:杀进程决策写入日志,供排障复盘。
为什么是“用户空间”
把回收策略放在用户空间,意味着可以读取 Framework 的进程优先级、应用配置甚至业务 语义,比内核驱动的静态阈值灵活得多。代价是多一层进程间通信,但低内存事件本身频率低, 这个开销可以接受。
源码证据
阅读目标:确认 lmkd 的入口与决策逻辑。
正文指针:system/memory/lmkd/lmkd.c 的主循环与 kill 决策; Framework 侧 ProcessList.java 向 lmkd 上报进程信息。
// lmkd.c(伪代码,行号以 r75 为准)
static void main_loop(void) {
// 监听内核压力事件与 socket 命令
}
static int find_and_kill_process(...) {
// 按 oom_adj 从高到低挑选并杀进程
}这段代码证明: lmkd 自己决定“杀谁、何时杀”;Framework 通过 socket 向它注册进程 信息。内核 OOM 只在 lmkd 之外兜底。
验证与排障
环境:开发设备或模拟器;查看 lmkd 日志不需要 root。
adb logcat -v threadtime | grep -E "lmkd|am_kill|lowmemorykiller"
adb shell cat /proc/pressure/memory 2>/dev/null
adb shell dumpsys meminfologcat 中的 am_kill 记录“哪个进程、什么 adj、为什么被杀”;/proc/pressure/memory 看内存压力指标;dumpsys meminfo 看当前占用。应用“莫名被杀”时,先找 am_kill 日志, 确认 adj 与触发原因,再决定是优化内存还是调整进程优先级策略。
常见误区
- “是内核 OOM 杀了应用”:主力是 lmkd,内核 OOM 是兜底。
- “lmkd 会轮询内存”:它被动监听压力事件。
- “杀进程只看 adj”:还看内存与分组。
- “lmkd 日志没用”:am_kill 是复盘杀进程的关键证据。
延伸问题
- PSI 与 vmpressure 有什么区别,为什么 Android 改用 PSI?
- minfree 档位如何与内存档位联动?
- lmkd 与 Framework 通过什么协议交换进程信息?
- 厂商如何调整低内存策略而不影响系统稳定性?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
system/memory/lmkd/lmkd.c | 主循环 / kill 决策 | 低内存回收 |
ProcessList.java | 进程信息上报 | Framework 协作 |
/proc/pressure/memory | PSI 接口 | 压力指标 |
am_kill 日志 | ActivityManager | 杀进程记录 |
公共路径:lmkd 在 system/memory/lmkd/,Framework 在 frameworks/base/services/core/java/com/android/server/am/。行号以 r75 检索为准。
进程分组与内存统计接口,见 cgroup 与内存控制。