在先前章節中,我們已瞭解核心如何管理整個系統的記憶體。本章將深入探討記憶體控制群組 (memcg),這是 Android 用於分割及控制個別應用程式記憶體用量的機制。
瞭解 memcg 至關重要,因為系統可以針對個別應用程式進行回收或設定記憶體限制,進而主動管理應用程式的記憶體用量。
記憶體控制群組 (memcg)
控制群組 (cgroup) 是 Linux 核心功能,可將程序組織成階層群組,並在群組之間分配系統資源 (例如 CPU、記憶體和 I/O)。memcg 是專門用於記憶體的 cgroup 控制器。
memcg 重要概念
- Memcg Charge:當 memcg 中的程序分配記憶體頁面 (匿名或檔案備份) 時,該頁面會「計入」memcg。memcg 的總費用是其中所有程序使用的所有頁面總和。
- 階層式記帳:記憶體用量會向上計入樹狀結構。子 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的值。 - 輕觸「Allocate Java Memory (10MB)」數次。
- 請再次閱讀
memory.current。每次輕觸後,記憶體用量應該會增加約 10 MB。 - 輕觸「Allocate Bitmaps」。查看
memory.stat,瞭解anon增幅。
使用 memory.reclaim 主動回收
memcg (v2) 的強大功能之一是 memory.reclaim 檔案。將值寫入這個檔案,核心就會立即嘗試從這個 memcg 和其下的任何 memcg 回收該數量的記憶體。
Android 如何使用memory.reclaim
Android 的 CachedAppOptimizer 會使用這項功能,在應用程式凍結後盡可能回收記憶體。Android App Freezer 可確保快取的應用程式在未執行時,盡可能減少 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 中,輕觸「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 和 Swap 計數器:
mem.rss.anon:輕觸分配按鈕後,這個值會增加。mem.swap:隨著kswapd和應用程式本身的執行緒 (受直接回收影響) 將這些匿名頁面壓縮到 ZRAM,此值會穩定增加。
- memcg 回收:如果放大觸發手動回收的時間點,會看到
rss.anon和rss.file急遽下降,並伴隨mm_vmscan_memcg_reclaim事件。
← 整個系統 | ↑ 向上 | kswapd 和 lmkd 互動 →