Android 的内存管理横跨内核、native 守护进程和 Java 框架三层。从虚拟内存的基础概念(VMA、页表、mmap),到 lmkd 守护进程的 PSI(Pressure Stall Information)监控,再到 AMS 的 OOM 调整算法——理解这一整套机制对性能优化和稳定性问题排查至关重要。
一、虚拟内存基础
1.1 VMA(Virtual Memory Area)
每个 Linux 进程都有一个独立的虚拟地址空间,由 vm_area_struct(VMA)来描述每个映射区域:
// include/linux/mm_types.h |
在 /proc/<pid>/maps 文件中可以看到进程的所有 VMA 映射,例如:
12c00000-12c40000 rw-p 00000000 00:00 0 [anon:dalvik-main space] |
1.2 mmap 系统调用
mmap 是内存管理中最关键的系统调用之一,它将文件或匿名内存映射到进程的地址空间:
// mm/mmap.c |
Android 中的关键应用:
- Binder mmap:
ProcessState初始化时通过 mmap 映射内核缓冲区(默认 1MB - 8*4096) - ashmem mmap:匿名共享内存,用于 SurfaceFlinger 的 GraphicBuffer
- ART 的 dex mmap:将 DEX/VDEX 文件 mmap 到进程空间用于类加载
- 堆分配:通过
sbrk/mmap扩展进程堆(Dalvik/ART heap)
1.3 内存度量指标
| 指标 | 含义 | 查询方式 |
|---|---|---|
| VSS | 虚拟内存总量(含未分配的保留地址) | /proc/<pid>/status 中的 VmSize |
| RSS | 实际占用的物理内存(含共享库) | VmRSS |
| PSS | 按比例分摊共享内存后的物理内存 | VmPSS(需要遍历页表,更耗时) |
| USS | 进程独占的物理内存 | PSS - 共享内存分摊部分 |
计算复杂度:VSS < RSS < PSS < USS。lmkd 使用 RSS 做快速判断,AMS 的 updateOomAdjLocked 使用 PSS 做更精确的评估。
二、Android 的 Low Memory Killer 演进
2.1 内核 LMK(Android 9 及之前)
早期 Android 使用内核 LMK 驱动(drivers/staging/android/lowmemorykiller.c)。它基于空闲内存阈值直接杀进程,缺点是阈值调整粗粒度、缺乏上下文感知。
2.2 用户空间 lmkd(Android 10+)
Android 10 开始使用用户空间守护进程 lmkd。它通过 PSI(Pressure Stall Information)监控系统内存压力,同时结合 AMS 传来的 OOM 分数做决策:
lmkd 守护进程(native,system/core/lmkd/) |
AOSP 路径:system/core/lmkd/lmkd.cpp
2.3 PSI(Pressure Stall Information)
PSI 是 Linux 4.20+ 引入的内核特性,通过 /proc/pressure/ 暴露了系统因内存、IO、CPU 不足而导致的任务阻塞时间:
# cat /proc/pressure/memory |
some:至少有一个任务被阻塞full:所有任务都被阻塞(整个系统完全停滞)avg10/avg60/avg300:过去 10 秒/60 秒/300 秒的平均阻塞百分比
lmkd 通过 epoll 监控 /proc/pressure/memory,当内存压力超过阈值时触发回收动作。
三、oom_score_adj 体系
3.1 核心值定义
AMS 通过 ProcessList 定义了完整的 OOM 调整体系:
// frameworks/base/services/core/java/com/android/server/am/ProcessList.java |
3.2 AMS 中 OOM 调整的计算
// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java (Android 11+) |
3.3 OOM ADJ 的写入
// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java |
四、cgroup 内存控制
Android 使用 Linux cgroup 对进程进行内存分组管理。lmkd 可以通过 cgroup memory 子系统限制进程或进程组的内存使用:
# 进程的 cgroup 信息 |
当进程超过 memory.limit_in_bytes 时,内核会触发 cgroup OOM,lmkd 可以设定更细粒度的内存限制策略。
五、进程内存状态(procState)
除了 oom_adj,AMS 还维护进程状态(process state),用于更细粒度的管理和统计:
// frameworks/base/core/java/android/app/ActivityManager.java |
六、核心面试题
Q1:Android 为什么要从内核 LMK 迁移到用户空间 lmkd?
内核 LMK 的决策完全基于空闲内存阈值,不感知进程的实际重要性。例如,一个正在播放音乐的 Service 和一个缓存进程在内核 LMK 看来可能具有相同的优先级。用户空间 lmkd 结合了 AMS 的知识(哪个进程在前台、哪个有可见 Activity),以及内核的 PSI 信息,做出更智能的决策。此外,用户空间 lmkd 可以通过 cgroup 实现更灵活的内存限制策略。
Q2:PSS 和 RSS 的区别是什么?Android 为什么偏好使用 PSS 来评估进程内存占用?
RSS 统计进程占用的全部物理内存页(包括与其他进程共享的库如 libc.so)。PSS 将共享内存页按共享进程数按比例分摊(如果 5 个进程共享 100KB,每个进程 PSS 只记 20KB)。Android 使用 PSS 的原因:它更准确地反映了”如果杀这个进程,能腾出多少物理内存”。但 PSS 计算开销大(需要遍历所有页表),因此 AMS 只在必要时触发 PSS 收集。
Q3:oom_score_adj 的范围是多少?FOREGROUND_APP_ADJ=0,CACHED_APP_MAX_ADJ=906,这些值为什么这么定义?
Linux 内核的 oom_score_adj 范围是 [-1000, 1000]。Android 的 adj 值与其一一对应:
- 负值(-1000 到 -1):系统关键进程,不考虑 kill(NATIVE_ADJ=-1000, SYSTEM_ADJ=-900, PERSISTENT_PROC_ADJ=-800)
- 0-99:前台/可见进程
- 100-199:可感知进程
- 200-899:有后台 Service 的进程
- 900-906:纯缓存进程,最先被 kill
这种分段使得 lmkd 可以用阈值区间来控制回收策略,比如 “当内存压力达到 critical 时,先回收 CACHED_APP 层级的进程”。
AOSP 核心路径参考:
frameworks/base/services/core/java/com/android/server/am/ProcessList.javaframeworks/base/services/core/java/com/android/server/am/OomAdjuster.javaframeworks/base/services/core/java/com/android/server/am/ActivityManagerService.java(updateOomAdjLocked 方法)system/core/lmkd/lmkd.cppsystem/core/lmkd/libpsi/— PSI 监控库drivers/staging/android/lowmemorykiller.c— 旧内核 LMKmm/mmap.c— mmap 内核实现include/linux/mm_types.h— VMA 定义






