在前面的部分中,我们了解了内核如何在系统范围内管理内存。在本章中,我们将深入探讨内存控制组 (memcg),这是 Android 用于对各个应用的内存用量进行分区和控制的机制。
了解 memcg 至关重要,因为系统可以在此级别针对单个应用进行回收或设置内存限制,从而主动管理应用内存占用。
内存控制组 (memcg)
控制组 (cgroup) 是一项 Linux 内核功能,可将进程组织到分层组中,并在这些组之间分配系统资源(例如 CPU、内存和 I/O)。memcg 是专门用于内存的 cgroup 控制器。
memcg 的关键概念
- Memcg 扣减:当 memcg 中的进程分配一页内存(匿名或文件支持)时,该页会从 memcg 中“扣减”。memcg 的总费用是其中所有进程使用的所有页面的总和。
- 分层核算:内存用量会沿树状结构向上核算。子 cgroup 中的费用也会计入其父 cgroup 的用量。
- 内存限制:每个 memcg 都通过
memory.high支持软回收阈值,应用内存预算使用该阈值来触发有针对性的回收和分配限制,而无需调用 OOM 终止进程。它还通过memory.max支持硬上限,当回收无法将用量保持在上限以下时,系统内存限制会使用该上限来终止失控进程。 - 按 memcg 回收:当 memcg 超过其限制或系统需要内存时,内核可以针对特定 memcg 进行回收。这意味着逐出其文件页面或将其匿名页面交换到 ZRAM。
Android memcg 层次结构
Android 使用特定的层次结构来管理应用进程。这种结构使系统能够对不同类型的应用(例如前台应用与后台应用)应用不同的政策。

/sys/fs/cgroup/apps/:所有 Android 应用的根。uid_<UID>/:每个应用的用户 ID 目录。属于同一应用包的所有进程都共享此组。pid_<PID>/:每个单独进程以及从中派生的所有子进程的目录。这样可以对具有多个进程的应用进行精细控制和统计。
实操练习:探索 memcg
在本练习中,您将找到 MemoryLab 的 memcg 目录,并实时观察其内存用量。
1. 启动 MemoryLab
确保 MemoryLab 正在您的设备上运行。
2. 查找 MemoryLab 的 memcg
首先,获取正在运行的 MemoryLab 应用的 PID:
adb shell pidof com.android.memorylab
# Example output: 11672
现在,找到其 cgroup 目录。您可以在 /proc/<PID>/cgroup 中找到此信息:
adb shell cat /proc/11672/cgroup
# Example output: 0::/apps/uid_10274/pid_11672
在 cgroup v2 中,0:: 后面的路径表示相对于 /sys/fs/cgroup 的 memcg 层次结构。因此,完整路径为 /sys/fs/cgroup/apps/uid_10274/pid_11672/。
3. 读取 memcg 统计信息
进入该目录并查看密钥文件:
# Current memory usage (in bytes)
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current
# Example output:
# 1591324672
memory.current 显示当前向此 cgroup 收取的内存总量(以字节为单位)。
# Detailed statistics
adb shell cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.stat | head -n 10
# Example output:
# anon 1585098752
# file 741376
# kernel 5484544
# kernel_stack 393216
# pagetables 4583424
# sec_pagetables 0
# percpu 216
# sock 0
# vmalloc 4096
# shmem 20480
memory.stat 提供了以下细分:* anon:匿名内存(堆、栈)量。* file:文件支持的内存(页面缓存)量。*
swap:换出到 ZRAM 的内存量。
4. 观察变化
- 打开 MemoryLab。
- 记下
memory.current的值。 - 点按分配 Java 内存 (10MB) 几次。
- 再次阅读
memory.current。您应该会看到,每次点按后,该值都会增加大约 10MB。 - 点按 Allocate Bitmaps。查看
memory.stat,了解anon增幅。
使用 memory.reclaim 主动回收
memcg (v2) 的一项强大功能是 memory.reclaim 文件。向此文件写入值会指示内核立即尝试从此 memcg 及其下的任何 memcg 回收该数量的内存。
Android 如何使用 memory.reclaim
Android 的 CachedAppOptimizer 会使用此功能,在应用冻结后尽可能多地回收应用占用的内存。Android 应用冻结器可确保缓存的应用在不运行时消耗尽可能少的 RAM。当应用移至后台并被冻结时,系统会将应用当前的内存用量写入其 memory.reclaim 文件。这会强制内核逐出所有可能的文件页面,并将所有匿名页面交换到 ZRAM,从而最大限度地减少应用的常驻内存占用。
您可以通过读取 memory.current 并将其写回 memory.reclaim,手动实现相同的“最大回收”:
# Force reclaim of everything
adb shell "cat /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.current > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
练习:强制回收和跟踪
现在,我们将强制内核从 MemoryLab 回收内存,并在 Perfetto 跟踪记录中捕获该活动。
- 准备:确保 MemoryLab 有一些分配(Java 和位图)。
Start Tracing:使用此内嵌跟踪配置可捕获调度、回收和页面缓存事件。
# Use a config that captures scheduling, reclaim and page cache events adb shell perfetto -c - --txt -o /data/misc/perfetto-traces/reclaim.perfetto-trace <<EOF buffers: { size_kb: 131072 fill_policy: RING_BUFFER } data_sources: { config { name: "linux.ftrace" ftrace_config { ftrace_events: "sched/sched_switch" ftrace_events: "sched/sched_wakeup" ftrace_events: "kmem/rss_stat" ftrace_events: "mm_filemap_add_to_page_cache" ftrace_events: "mm_filemap_delete_from_page_cache" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_direct_reclaim_end" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_begin" ftrace_events: "vmscan/mm_vmscan_memcg_reclaim_end" symbolize_ksyms: true } } } data_sources: { config { name: "linux.process_stats" process_stats_config { scan_all_processes_on_start: true } } } EOF触发压力和回收:
- 在 MemoryLab 中,点按分配原生内存 (1GB)。这将触发系统级压力。
在另一个终端中,回收 200MB:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Fault Back In:切换回 MemoryLab。点按 Thrash Pagecache(Refault 测试)。
停止跟踪:在跟踪终端中按 Ctrl+C。
在 Perfetto 中分析回收
打开跟踪记录后,您可以看到系统级和应用级回收操作:

在跟踪记录中查找以下内容:
- kswapd:在内核线程下搜索
kswapd0(在上面的屏幕截图中,它被手动固定在顶部)。您会看到它在系统努力寻找可用页面时唤醒并运行(绿色切片)。 - 直接回收:查看
com.android.memorylab进程线程。 您会看到紫色的 ftrace 事件切片(例如mm_vmscan_direct_reclaim_begin)直接显示在线程的调度轨道下方。这表明应用线程本身因等待内核释放页面而停滞。 - RSS 和交换计数器:
mem.rss.anon:随着您点按分配按钮而增加。mem.swap:随着kswapd和应用自己的线程(受直接回收影响)将这些匿名页压缩到 ZRAM 中,该值会稳步增加。
- memcg 回收:如果您放大触发手动回收的时刻,会看到
rss.anon和rss.file急剧下降,并伴有mm_vmscan_memcg_reclaim事件。
← 系统级 | ↑ 向上 | kswapd 和 lmkd 交互 →