目录
  1. 1. 一、虚拟内存基础
    1. 1.1. 1.1 VMA(Virtual Memory Area)
    2. 1.2. 1.2 mmap 系统调用
      1. 1.2.1. 1.2.1 mmap 的内核实现简析
    3. 1.3. 1.3 页表与 MMU
    4. 1.4. 1.4 内存度量指标
      1. 1.4.1. 1.4.1 PSS 的计算方式
  2. 2. 二、Android 的 Low Memory Killer 演进
    1. 2.1. 2.1 内核 LMK(Android 9 及之前)
    2. 2.2. 2.2 用户空间 lmkd(Android 10+)
      1. 2.2.1. 2.2.1 lmkd 的主要回收策略
      2. 2.2.2. 2.2.2 lmkd 和 AMS 的协作
    3. 2.3. 2.3 PSI(Pressure Stall Information)
      1. 2.3.1. 2.3.1 PSI 阈值配置
  3. 3. 三、oom_score_adj 体系
    1. 3.1. 3.1 核心值定义
    2. 3.2. 3.2 AMS 中 OOM 调整的计算
    3. 3.3. 3.3 OOM ADJ 的写入
      1. 3.3.1. 3.3.1 oom_score_adj 的内核实现
  4. 4. 四、cgroup 内存控制
    1. 4.0.1. 4.1 cgroup freezer(Android 11+)
  • 5. 五、进程内存状态(procState)
  • 6. 六、ART 的堆内存管理
    1. 6.1. 6.1 ART 堆的分配策略
    2. 6.2. 6.2 GC 策略演进
      1. 6.2.1. 6.2.1 Concurrent Copying GC 原理
      2. 6.2.2. 6.2.2 Generational CC GC (Android 10+)
  • 7. 七、核心面试题
  • 【深入内核篇】内存管理基础

    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.2.1 mmap 的内核实现简析

    // mm/mmap.c (简化)
    unsigned long do_mmap(struct file *file, unsigned long addr,
    unsigned long len, unsigned long prot,
    unsigned long flags, unsigned long pgoff,
    unsigned long *populate)
    {
    struct mm_struct *mm = current->mm;

    // 1. 在进程的虚拟地址空间中查找可用的地址范围
    addr = get_unmapped_area(file, addr, len, pgoff, flags);

    // 2. 创建 VMA 并初始化
    struct vm_area_struct *vma = vm_area_alloc(mm);
    vma->vm_start = addr;
    vma->vm_end = addr + len;
    vma->vm_flags = calc_vm_prot_bits(prot, flags) | ...;
    vma->vm_page_prot = vm_get_page_prot(vma->vm_flags);

    // 3. 如果是文件映射,设置文件操作
    if (file) {
    vma->vm_file = get_file(file);
    vma->vm_ops = file->f_op->mmap; // 设置文件特定的 mmap 操作
    }

    // 4. 插入 VMA 到进程的 mm_struct 的红黑树中
    vma_link(mm, vma, prev, rb_link, rb_parent);

    return addr;
    }

    1.3 页表与 MMU

    虚拟地址到物理地址的转换由 MMU(Memory Management Unit)通过页表完成:

    虚拟地址 (Virtual Address)


    ┌─────────┬─────────┬──────────┐
    │ PGD idx │ PUD idx │ PMD idx │ PTE idx │ offset │
    │ (9bit) │ (9bit) │ (9bit) │ (9bit) │ (12bit)│
    └────┬────┴────┬────┴────┬─────┴────┬────┴───┬────┘
    │ │ │ │ │
    ▼ ▼ ▼ ▼ │
    PGD ──► PUD ──► PMD ──► PTE ──► Physical Page Frame


    Physical Memory

    在 ARM64 平台上,通常使用 4 级页表(PGD -> PUD -> PMD -> PTE),每级 9 位索引(支持 48 位虚拟地址,即 256TB)。页大小通常是 4KB(也可以使用 16KB 或 64KB huge page)。

    TLB(Translation Lookaside Buffer)缓存了最近使用的虚拟-物理映射,是 MMU 性能的关键。TLB miss 会导致遍历页表(walk page tables),代价昂贵。

    1.4 内存度量指标

    指标 含义 查询方式 计算复杂度
    VSS 虚拟内存总量(含未分配的保留地址) /proc/<pid>/status 中的 VmSize O(1)
    RSS 实际占用的物理内存(含共享库) VmRSS O(1)
    PSS 按比例分摊共享内存后的物理内存 VmPSS(需要遍历页表,更耗时) O(n)
    USS 进程独占的物理内存 PSS - 共享内存分摊部分 O(n)

    计算复杂度:VSS < RSS < PSS < USS。lmkd 使用 RSS 做快速判断,AMS 的 updateOomAdjLocked 使用 PSS 做更精确的评估。

    1.4.1 PSS 的计算方式

    // 内核中 PSS 的计算(简化)
    // fs/proc/task_mmu.c
    static void smaps_pte_entry(pte_t *pte, unsigned long addr,
    struct mm_walk *walk) {
    struct mem_size_stats *mss = walk->private;
    struct page *page;

    if (pte_present(*pte)) {
    page = pte_page(*pte);
    // RSS:直接累加
    mss->resident += PAGE_SIZE;
    // PSS:按共享进程数按比例分摊
    mss->pss += PAGE_SIZE / page_count(page);
    // USS(独占):如果页面只被当前进程引用
    if (page_mapcount(page) <= 1) {
    mss->private_clean += PAGE_SIZE;
    }
    }
    }

    举例:如果 libc.so 的一个 4KB 页被 10 个进程共享,那么:

    • 每个进程的 RSS +4KB
    • 每个进程的 PSS +0.4KB (4KB / 10)
    • 每个进程的 USS +0KB(因为不是独占)

    二、Android 的 Low Memory Killer 演进

    2.1 内核 LMK(Android 9 及之前)

    早期 Android 使用内核 LMK 驱动(drivers/staging/android/lowmemorykiller.c)。它基于空闲内存阈值直接杀进程,缺点是阈值调整粗粒度、缺乏上下文感知。

    内核 LMK 的工作方式:

    // drivers/staging/android/lowmemorykiller.c (旧版)
    static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) {
    // 根据当前空闲内存量,在预设的阈值数组中找到对应的 min_adj
    // 然后杀掉 oom_score_adj >= min_adj 的进程
    for (i = 0; i < array_size; i++) {
    if (lowmem_minfree[i] > other_free || ...) {
    min_score_adj = lowmem_adj[i];
    break;
    }
    }
    // 选择 oom_score_adj 最大的进程并发送 SIGKILL
    selected_oom_score_adj = ...;
    send_sig(SIGKILL, selected_task, 0);
    }

    内核 LMK 的核心缺陷:

    1. 只关心空闲内存:不感知进程是否对用户可见,可能误杀前台应用。
    2. 阈值静态配置/sys/module/lowmemorykiller/parameters/minfree 中的阈值需要设备制造商根据 RAM 大小预设,无法动态调整。
    3. 无上下文感知:不知道哪个进程是后台音乐播放,哪个是纯粹缓存。

    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.2.1 lmkd 的主要回收策略

    // system/core/lmkd/lmkd.cpp (简化)
    static void mp_event_common(int data, uint32_t events) {
    // 1. 从 PSI 监控获取内存压力
    bool critical = (events & PSI_CRITICAL) != 0;

    // 2. 根据压力级别选择回收策略
    if (critical) {
    // Critical 压力:立即杀 CACHED_APP
    find_and_kill_processes(CACHED_APP_MAX_ADJ, ...);
    } else {
    // Moderate 压力:渐进式回收
    // 先尝试 reclaim(回收文件页),再尝试 kill
    find_and_kill_processes(PERCEPTIBLE_APP_ADJ, ...);
    }
    }

    // lmkd 的 vmpressure 策略(替代 PSI,Android 10 早期版本)
    static void vmpressure_handler(int level) {
    switch (level) {
    case VMPRESSURE_LEVEL_LOW:
    // 轻微压力:杀 CACHED_APP
    break;
    case VMPRESSURE_LEVEL_MEDIUM:
    // 中等压力:杀 SERVICE 以下
    break;
    case VMPRESSURE_LEVEL_CRITICAL:
    // 严重压力:杀 PERCEPTIBLE 以下
    break;
    }
    }

    2.2.2 lmkd 和 AMS 的协作

    AMS 通过 lmkd socket 向 lmkd 发送进程的 OOM 分数变更:

    // frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java
    private void applyOomAdjLocked(ProcessRecord app, ...) {
    // 1. 计算进程的 oom_score_adj
    int oomAdj = computeOomAdjLocked(app, ...);

    // 2. 写入 /proc/<pid>/oom_score_adj
    FileUtils.stringToFile("/proc/" + app.pid + "/oom_score_adj",
    Integer.toString(oomAdj));

    // 3. 通过 lmkd socket 通知 lmkd 守护进程
    if (mAppProfiler.mLmkdConnection != null) {
    LmkdStats.sendOomScoreAdjUpdate(mAppProfiler.mLmkdConnection,
    app.pid, app.uid, oomAdj);
    }
    }

    lmkd 也可以通过 socket 主动请求 AMS 降低某个进程的 OOM 分数(使其优先被杀):

    lmkd ----[LMK_PROCPRIO]----> AMS
    "这个进程的 OOM 分数太低,请调整到 800"

    AMS ----[LMK_PROCREMOVE]--> lmkd
    "这个进程已经不存在了,请移除"

    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,当内存压力超过阈值时触发回收动作。

    // system/core/lmkd/libpsi/psi.cpp
    bool PSIMonitor::open() {
    mFd = TEMP_FAILURE_RETRY(::open(
    "/proc/pressure/memory", O_RDONLY | O_CLOEXEC));
    return mFd >= 0;
    }

    void PSIMonitor::poll() {
    // 通过 epoll 等待 PSI 事件
    epoll_event events[MAX_EPOLL_EVENTS];
    int nevents = epoll_wait(mEpollFd, events, MAX_EPOLL_EVENTS, timeout_ms);

    for (int i = 0; i < nevents; i++) {
    // 解析 PSI 事件:判断是否达到 some/full 阈值
    processPsiEvent(events[i]);
    }
    }

    2.3.1 PSI 阈值配置

    lmkd 的 PSI 阈值是可配置的:

    # /vendor/etc/lmkd.conf (vendor 配置)
    # PSI 阈值(百分比 * 100),达到后触发回收
    ro.lmk.low=100 # 轻微压力 (1%)
    ro.lmk.medium=800 # 中等压力 (8%)
    ro.lmk.critical=1500 # 严重压力 (15%)

    PSI 比旧的内核 LMK 的优势:

    • 精确感知:PSI 直接度量任务的阻塞时间,而非间接的空闲内存量。
    • 分级响应:some/full 提供不同的压力级别,lmkd 可以渐进式响应。
    • 提前预警:在系统完全 OOM 之前就采取行动(full 压力发生之前)。

    三、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));
    }

    3.3.1 oom_score_adj 的内核实现

    // fs/proc/base.c
    static ssize_t oom_score_adj_write(struct file *file, ...) {
    struct task_struct *task;
    int oom_score_adj;

    // 1. 从用户空间读取新值(范围校验 -1000 到 1000)
    oom_score_adj = ...;
    if (oom_score_adj < -1000 || oom_score_adj > 1000)
    return -EINVAL;

    // 2. 更新进程的 oom_score_adj
    task->signal->oom_score_adj = oom_score_adj;

    // 3. 重新计算 OOM 总分
    // oom_badness() = 进程的内存占用 * (oom_score_adj / 1000)
    // adj 越高(正数),越容易被杀;adj 越低(负数),越不容易被杀
    }

    // mm/oom_kill.c
    unsigned long oom_badness(struct task_struct *p, ...) {
    unsigned long points;

    // 基础分 = 进程占用的物理内存页面数
    points = get_mm_rss(p->mm) + ...;

    // 乘以 oom_score_adj 归一化因子
    // adj = -1000 → 降低一半分数 → 极难被 OOM kill
    // adj = 1000 → 增加一半分数 → 极易被 OOM kill
    adj = (long)p->signal->oom_score_adj;
    points = points * (1000 + adj) / 1000;

    return points > 0 ? points : 1;
    }

    四、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 可以设定更细粒度的内存限制策略。

    Android 的 cgroup 层次结构:

    /sys/fs/cgroup/
    ├── memory/
    │ ├── foreground/ # 前台应用
    │ │ ├── uid_1000/
    │ │ └── uid_1001/
    │ ├── background/ # 后台应用
    │ └── system/ # 系统服务
    ├── cpu/
    │ ├── foreground/
    │ └── background/
    ├── cpuset/
    │ ├── foreground/
    │ ├── background/
    │ └── top-app/ # 前台顶级应用
    └── freezer/ # Android 11+ 的进程冻结
    ├── frozen/
    └── unfrozen/

    4.1 cgroup freezer(Android 11+)

    Android 11 引入了 cgroup freezer 来替代之前的 SIGSTOP/CON 信号:

    // frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
    static void freezeProcess(int pid, int uid) {
    // 将进程移入 frozen cgroup
    // echo pid > /sys/fs/cgroup/freezer/frozen/cgroup.procs
    // 进程的所有线程都会被冻结(无法被调度)
    }

    static void unfreezeProcess(int pid, int uid) {
    // 将进程移出 frozen cgroup
    // echo pid > /sys/fs/cgroup/freezer/unfrozen/cgroup.procs
    }

    cgroup freezer 的优势:

    • 比信号方式更可靠(SIGSTOP 可以被捕获或忽略)
    • 可冻结线程组(整个进程的所有线程)
    • 内核级别的保证(冻结的进程无法分配 CPU 时间)

    五、进程内存状态(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; // 空进程

    六、ART 的堆内存管理

    6.1 ART 堆的分配策略

    // art/runtime/gc/heap.cc
    // ART 堆分为多个 Space(空间)
    class Heap {
    // 堆的主要空间类型:
    ImageSpace* image_space_; // Boot Image(预加载的 framework 类)
    M allocSpace* alloc_space_; // RosAlloc / dlmalloc(小对象分配)
    LargeObjectSpace* los_; // 大对象空间(>= 12KB 的对象)
    ZygoteSpace* zygote_space_; // fork 自 Zygote 的共享空间
    BumpPointerSpace* region_space_; // 区域空间(Android 10+ 的 CC GC)
    };

    ART 堆结构图:

    ┌─────────────────────────────────────┐ 高地址
    │ Large Object Space │ 对象 >= 12KB
    │ (free list allocator) │
    ├─────────────────────────────────────┤
    │ Non-moving Space │
    │ (dlmalloc / RosAlloc) │
    ├─────────────────────────────────────┤
    │ Zygote Space (共享) │
    │ (从 Zygote 继承的预加载类) │
    ├─────────────────────────────────────┤
    │ Image Space (共享) │
    │ (boot.art, boot-framework) │
    └─────────────────────────────────────┘ 低地址

    6.2 GC 策略演进

    ART 的 GC 经历了多次重大改进:

    1. **Dalvik (Android 4.4 以前)**:Mark-Sweep,非压缩,stop-the-world。
    2. **ART (Android 5.0-5.1)**:Concurrent Mark-Sweep (CMS),部分并发,减少暂停时间。
    3. **ART (Android 6.0-7.x)**:引入 Compacting GC (Semi-Space) 用于前台应用,后台应用仍用 CMS。
    4. **ART (Android 8.0-9.x)**:Concurrent Copying (CC) GC,基于 Read Barrier,最小化暂停时间。
    5. **ART (Android 10+)**:Generational Concurrent Copying (GCC) GC,基于分代假设,进一步提升吞吐。

    6.2.1 Concurrent Copying GC 原理

    // art/runtime/gc/collector/concurrent_copying.cc
    class ConcurrentCopying : public GarbageCollector {
    void RunPhases() {
    // 1. 初始化阶段 (STW - 暂停所有线程)
    InitializePhase();

    // 2. 标记阶段 (并发 - 与 mutator 线程并发执行)
    MarkingPhase(); // 从 roots 遍历对象图

    // 3. 暂停阶段 (STW)
    PausePhase(); // 暂停以完成标记和计算转发地址

    // 4. 复制阶段 (并发 - 使用 Read Barrier)
    CopyPhase(); // 将存活对象复制到新区域

    // 5. 回收阶段 (并发)
    ReclaimPhase(); // 回收旧区域的内存
    }
    };

    Read Barrier(读屏障)是 CC GC 的核心技术。当线程读取一个对象引用时,如果该对象已经被复制到新区域,读屏障会自动将引用更新到新地址:

    线程读取 obj.field


    obj 是否已移动?
    / \
    是 否
    │ │
    ▼ ▼
    返回新地址 返回原地址
    (Read Barrier) (无屏障开销)

    6.2.2 Generational CC GC (Android 10+)

    基于分代假设(大多数对象很快死亡),将堆分为 Young Generation 和 Old Generation:

    Young Generation (Region Space)
    ├── 新分配的对象
    ├── Minor GC 频繁发生(快速回收短生命周期对象)
    └── 存活一定次数的对象晋升到 Old Generation

    Old Generation
    ├── 长期存活的对象
    ├── Major GC 偶尔发生(全面回收)
    └── 写屏障记录 Young->Old 引用

    通过分代回收,可以减少扫描的对象数量,降低 GC 暂停时间。

    七、核心面试题

    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 层级的进程”。

    Q4:mmap 和 brk 有什么区别?Android 的 ART 堆使用哪种方式分配内存?

    • brk/sbrk:通过调整进程的数据段末尾指针来分配/释放内存。只能分配连续的内存,不能释放中间的内存块(只能从末尾释放)。适用于小块内存的快速分配。
    • mmap:在进程地址空间的任意位置映射一块内存。可以独立释放(通过 munmap),更灵活但比 brk 慢(需要操作 VMA 和页表)。

    ART 堆的分配策略:

    1. 小对象(< 12KB):使用 RosAlloc 或 dlmalloc(基于 brk 和 mmap 混合)。
    2. 大对象(>= 12KB):使用 mmap 分配(Large Object Space),按页大小对齐。
    3. Image Space / Zygote Space:使用 mmap 映射预编译的 .art 文件。

    Q5:cgroup freezer 和 SIGSTOP 冻结进程有什么区别?

    • SIGSTOP:发送信号给进程,进程被暂停。但进程可以捕获并处理信号,可能不完全停止。某些系统调用在收到信号后会自动重试。如果进程被 SIGSTOP 时正在持有锁,可能导致死锁。
    • cgroup freezer:内核级别的冻结机制。通过将进程移入 frozen cgroup,内核调度器停止调度该进程的所有线程。进程无法感知到被冻结(无信号传递),不会造成锁持有问题。冻结和解冻都是原子操作,更可靠。

    Android 11+ 使用 cgroup freezer 来替代 CachedAppOptimizer 中的 SIGSTOP 机制,提供了更可靠的进程冻结能力。

    核心参考 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
    • 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 定义
    • art/runtime/gc/heap.cc — ART 堆管理
    • art/runtime/gc/collector/concurrent_copying.cc — CC GC 实现
    打赏
    • 微信
    • 支付宝

    评论