在先前的章節中,我們瞭解了核心如何管理整個系統的記憶體。在本章中,我們將深入探討 記憶體控制群組 (memcg),這是 Android 用來分割及控管個別應用程式記憶體用量的機制。
瞭解 memcg 至關重要,因為系統可在此層級針對個別應用程式進行回收或設定記憶體限制,主動管理應用程式的記憶體用量。
記憶體控制群組 (memcg)
控制群組 (cgroups) 是 Linux 核心功能,可將程序整理成階層式群組,並在這些群組之間分配系統資源 (例如 CPU、記憶體和 I/O)。memcg 是專為記憶體設計的 cgroup 控制器。
memcg 重要概念
- Memcg Charge:當 memcg 中的程序分配記憶體頁面 (匿名或檔案支援) 時,該頁面會「計入」memcg。memcg 的總費用是其中所有程序使用的所有頁面總和。
- 階層式會計:記憶體用量會計入樹狀結構。子項 Cgroup 的費用也會計入父項的使用量。
- 記憶體限制:每個 memcg 都可以設有限制 (例如
memory.max或memory.high),如果超出限制,就會觸發回收程序,甚至啟動 OOM 終止程序,不受全域系統記憶體影響。 - 每個 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的值。 - 輕觸「Allocate Java Memory (10MB)」(配置 Java 記憶體 (10MB)) 數次。
- 請再次閱讀
memory.current。每次輕觸時,這個值應該會增加約 10 MB。 - 輕觸「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 和點陣圖)。
開始追蹤:使用這個內嵌追蹤設定,擷取排程、回收和網頁快取事件。
# 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 中,輕觸「Allocate Native Memory (1GB)」。這會觸發全系統壓力。
在另一個終端機中,回收 200 MB:
adb shell "echo 200M > /sys/fs/cgroup/apps/uid_10274/pid_11672/memory.reclaim"
Fault Back In:切換回 MemoryLab。輕觸「Thrash Pagecache (Refault test)」。
停止追蹤:在追蹤終端機中按下 Ctrl+C。
在 Perfetto 中分析回收作業
開啟追蹤記錄後,您會看到系統和每個應用程式的回收作業:

在追蹤記錄中找出下列項目:
- kswapd:在「Kernel threads」(核心執行緒) 下搜尋
kswapd0(在上方螢幕截圖中,這是手動釘選在頂端的項目)。您會看到系統努力尋找可用頁面時,會喚醒並執行 (綠色切片)。 - 直接回收:查看
com.android.memorylab程序執行緒。 您會看到紫色 ftrace 事件切片 (例如mm_vmscan_direct_reclaim_begin) 直接顯示在執行緒的排程軌下方。這表示應用程式執行緒本身處於停滯狀態,正在等待核心釋出頁面。 - RSS 和交換計數器:
mem.rss.anon:輕觸分配按鈕時會增加。mem.swap:隨著kswapd和應用程式本身的執行緒 (須直接回收) 將匿名頁面壓縮到 ZRAM,這個值會穩定增加。
- memcg Reclaim:如果放大手動觸發回收的時刻,您會看到
rss.anon和rss.file雙雙急遽下降,並伴隨mm_vmscan_memcg_reclaim事件。
← 系統層級 | ↑ 向上 | kswapd 和 lmkd 互動 →