目录
  1. 1. 一、虚拟内存基础
    1. 1.1. 1.1 VMA(Virtual Memory Area)
    2. 1.2. 1.2 mmap 系统调用
    3. 1.3. 1.3 内存度量指标
  2. 2. 二、Android 的 Low Memory Killer 演进
    1. 2.1. 2.1 内核 LMK(Android 9 及之前)
    2. 2.2. 2.2 用户空间 lmkd(Android 10+)
    3. 2.3. 2.3 PSI(Pressure Stall Information)
  3. 3. 三、oom_score_adj 体系
    1. 3.1. 3.1 核心值定义
    2. 3.2. 3.2 AMS 中 OOM 调整的计算
    3. 3.3. 3.3 OOM ADJ 的写入
  4. 4. 四、cgroup 内存控制
  5. 5. 五、进程内存状态(procState)
  6. 6. 六、核心面试题
【深入内核篇】内存管理基础

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
struct vm_area_struct {
unsigned long vm_start;
unsigned long vm_end;
struct mm_struct *vm_mm;
pgprot_t vm_page_prot;
unsigned long vm_flags; // VM_READ | VM_WRITE | VM_EXEC 等
const struct vm_operations_struct *vm_ops;
// ...
};

/proc/<pid>/maps 文件中可以看到进程的所有 VMA 映射,例如:

12c00000-12c40000 rw-p 00000000 00:00 0          [anon:dalvik-main space]
7000000000-7000001000 r--p 00000000 103:09 1234 /system/lib64/libc.so

1.2 mmap 系统调用

mmap 是内存管理中最关键的系统调用之一,它将文件或匿名内存映射到进程的地址空间:

// mm/mmap.c
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);

Android 中的关键应用:

  • Binder mmapProcessState 初始化时通过 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/)
├── 读取 /proc/pressure/memory 监控内存压力
├── 通过 /proc/<pid>/oom_score_adj 读取进程状态
├── 使用 cgroup memory 限制或直接 kill 进程
└── 通过 lmkd socket 与 AMS 通信(AMS 主动请求杀进程)

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 avg10=0.00 avg60=0.00 avg300=0.00 total=0
full avg10=0.00 avg60=0.00 avg300=0.00 total=0
  • 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

// 原生 adj 值(对应内核 oom_score_adj,范围 -1000 到 1000)
static final int NATIVE_ADJ = -1000;

// Android 系统定义的 adj 值
static final int UNKNOWN_ADJ = 1001; // 未知
static final int CACHED_APP_MAX_ADJ = 906;// 最不可见的缓存进程
static final int CACHED_APP_MIN_ADJ = 900;// 缓存进程门槛
static final int SERVICE_B_ADJ = 800; // 包含后台 Service 的进程
static final int PREVIOUS_APP_ADJ = 700; // 上一个使用的 App
static final int HOME_APP_ADJ = 600; // Launcher
static final int SERVICE_ADJ = 500; // 有已启动 Service 的进程
static final int HEAVY_WEIGHT_APP_ADJ = 400;
static final int BACKUP_APP_ADJ = 300; // 备份 App
static final int PERCEPTIBLE_APP_ADJ = 200;// 可感知(前台 Service、正在播放音乐等)
static final int VISIBLE_APP_ADJ = 100; // 可见但不在前台
static final int FOREGROUND_APP_ADJ = 0; // 前台 App
static final int PERSISTENT_SERVICE_ADJ = -700;
static final int PERSISTENT_PROC_ADJ = -800;
static final int SYSTEM_ADJ = -900; // system_server
static final int NATIVE_ADJ = -1000; // native 守护进程

3.2 AMS 中 OOM 调整的计算

// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java (Android 11+)
// 或 AMS.updateOomAdjLocked() (Android 10 及之前)

void updateOomAdjLocked() {
// 1. 遍历所有进程
for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
ProcessRecord app = mLruProcesses.get(i);

// 2. 根据进程内组件状态计算 adj
if (app.isKilledByAm() || app.thread == null) {
// 已死亡或未就绪的进程
app.curAdj = UNKNOWN_ADJ;
} else if (app == mPreviousProcess && app.hasActivities()) {
app.curAdj = PREVIOUS_APP_ADJ;
} else if (app.mState.hasForegroundActivities()) {
app.curAdj = FOREGROUND_APP_ADJ;
} else if (app.mState.hasVisibleActivities()) {
app.curAdj = VISIBLE_APP_ADJ;
} else if (app.mState.hasForegroundServices()) {
app.curAdj = PERCEPTIBLE_APP_ADJ;
} else if (app.mState.hasStartedServices()) {
app.curAdj = SERVICE_ADJ;
} else if (app.mState.hasClientActivities()) {
app.curAdj = VISIBLE_APP_ADJ;
} else {
app.curAdj = CACHED_APP_MIN_ADJ;
}

// 3. 将计算出的 adj 写入 /proc/<pid>/oom_score_adj
applyOomAdjLocked(app, true, now, nowElapsed);
}
}

3.3 OOM ADJ 的写入

// frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
private boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll,
long now, long nowElapsed) {
// 写入 /proc/<pid>/oom_score_adj
// 值越大越容易被 kill
FileUtils.stringToFile("/proc/" + app.pid + "/oom_score_adj",
Integer.toString(app.curAdj));

// 同时写入 /proc/<pid>/oom_score
// oom_score = oom_score_adj * (1000 / OOM_SCORE_ADJ_MAX)
int oomScore = (int)((app.curAdj * OOM_SCORE_ADJ_MAX) / 1000);
FileUtils.stringToFile("/proc/" + app.pid + "/oom_score",
Integer.toString(oomScore));
}

四、cgroup 内存控制

Android 使用 Linux cgroup 对进程进行内存分组管理。lmkd 可以通过 cgroup memory 子系统限制进程或进程组的内存使用:

# 进程的 cgroup 信息
cat /proc/<pid>/cgroup

# cgroup memory 限制(lmkd 可以设置)
echo <limit_in_bytes> > /sys/fs/cgroup/memory/<group>/memory.limit_in_bytes

当进程超过 memory.limit_in_bytes 时,内核会触发 cgroup OOM,lmkd 可以设定更细粒度的内存限制策略。

五、进程内存状态(procState)

除了 oom_adj,AMS 还维护进程状态(process state),用于更细粒度的管理和统计:

// frameworks/base/core/java/android/app/ActivityManager.java
public static final int PROCESS_STATE_NONEXISTENT = -1;
public static final int PROCESS_STATE_TOP = 2; // 前台顶级 Activity
public static final int PROCESS_STATE_BOUND_TOP = 3;
public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 5;
public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
public static final int PROCESS_STATE_BACKUP = 9;
public static final int PROCESS_STATE_HEAVY_WEIGHT = 10;
public static final int PROCESS_STATE_SERVICE = 11;
public static final int PROCESS_STATE_RECEIVER = 12;
public static final int PROCESS_STATE_HOME = 13;
public static final int PROCESS_STATE_LAST_ACTIVITY = 14;
public static final int PROCESS_STATE_CACHED_ACTIVITY = 15;
public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
public static final int PROCESS_STATE_CACHED_RECENT = 17;
public static final int PROCESS_STATE_CACHED_EMPTY = 18; // 空进程

六、核心面试题

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.java
  • frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
  • frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java(updateOomAdjLocked 方法)
  • system/core/lmkd/lmkd.cpp
  • system/core/lmkd/libpsi/ — PSI 监控库
  • drivers/staging/android/lowmemorykiller.c — 旧内核 LMK
  • mm/mmap.c — mmap 内核实现
  • include/linux/mm_types.h — VMA 定义
打赏
  • 微信
  • 支付宝

评论