在 Android 17 以上版本中,平台會使用兩個互補的 Linux cgroup v2 機制,與一般應用程式記憶體限制器搭配運作,控管系統程序記憶體:
- 系統 Daemon 保護下限 (
SystemMemoryBudgetManager搭配memory.min):自我調整 cgroup v2memory.min保留 (設為每個 Daemon 穩定常駐工作集的 90%,全域上限為實體 RAM 總量的 10%),可在記憶體壓力下保護重要的互動式 Daemon 和殼層程序 (system_server、surfaceflinger、圖形composerHAL、audioserver、cameraserver、netd、SystemUI和主畫面啟動器),取代靜態PinnerServicemlock檔案固定。 - 系統應用程式記憶體上限 (
<memory-budget>搭配memory.high): 在全域記憶體壓力發生前,針對永久和背景系統應用程式 (Bluetooth、Nfc、CalendarProvider、Traceur、MediaProvider、:PhotoPicker、NetworkStack、AppSearch和TeleService),在資訊清單檔案中宣告的工作負載校正 cgroup v2memory.high軟限制 (通常為 30 MB 至 190 MB),會主動回收冷檔案頁面快取和突發後匿名堆積。
下表比較這兩種機制:
| 維度 | 系統 Daemon 保護下限 | 系統應用程式記憶體上限 |
|---|---|---|
| 平台服務 | SystemMemoryBudgetManager |
MemoryLimiter |
| Cgroup v2 控制項 | memory.min 硬性保護底價 |
memory.high 軟回收上限 |
| 目標程序 | 對延遲時間要求嚴格的互動式 Daemon 和殼層 UI (system_server、surfaceflinger、composer HAL、audioserver、cameraserver、netd、SystemUI 和 Google Home 應用程式) |
永久和無頭系統應用程式、內容供應器和背景服務 (例如 Bluetooth、Nfc、MediaProvider、NetworkStack、AppSearch 和 TeleService) |
| 規模調整模型 | 執行階段自我調整 (90% 的固定工作集,全球上限為總 RAM 的 10%),不需為每部裝置設定調整表 | 根據 RAM 級別、程序狀態和顯示像素數量校正的宣告式 <memory-budget> 資訊清單 XML 屬性 |
| 主要優點 | 在記憶體壓力下,防止互動式 UI 資源浪費和喚醒停滯,同時取代靜態 PinnerService mlock 檔案清單 |
持續清除冷一次性檔案快取和突發後堆積,為前景和快取應用程式保留更多實體 RAM |
如要進一步瞭解 <memory-budget> 語法、XML 屬性、執行階段 API 和 OS 記憶體計算,請參閱「設定應用程式記憶體預算」和「瞭解 Android 記憶體及排解相關問題」。
使用 SystemMemoryBudgetManager 保護重要的互動式 Daemon
SystemMemoryBudgetManager 會在記憶體壓力期間,將直接位於使用者互動路徑上的程序保留在實體 RAM 中。SystemMemoryBudgetManager 不會使用 mlock 將 APK、ODEX 和 VDEX 檔案的靜態清單固定到記憶體 (如 PinnerService 對 config_defaultPinnerServiceFiles 和 config_pinnerHomePinBytes 的做法),而是會將動態 cgroup v2 memory.min 下限指派給下列程序:
- 核心系統切片 (
/sys/fs/cgroup/system):system_server、surfaceflinger、圖形composerHAL、audioserver、cameraserver和netd,以及config_systemMemoryBudgetAdditionalSystemProcesses中列出的任何板型規格專用原生系統或供應商 Daemon (例如 XR 裝置上的android.hardware.perceptionhost和auracamerahal)。 - 互動式應用程式切片 (
/sys/fs/cgroup/apps):已設定的 SystemUI 套件 (config_systemUi) 的主要程序,以及目前使用者主畫面角色的主要程序。
除了 config_systemUi 之外,也保護 Home 角色持有者,確保所有板型規格都受到保護。在 config_systemUi 為輕量相容性套件的外型規格上 (例如 Wear OS,錶面、通知和資訊方塊在 Google Home 應用程式中執行),面向使用者的殼層仍會受到完整工作集保護。如果 Home 意圖解析為 config_systemUi 套件,程序會受到一次保護。只有每個套件的主程序 (processName == packageName) 會獲得保護底限,因此輔助子程序 (例如 :screenshot) 不會消耗保護預算。
自我調整政策和三層 cgroup 委派
由於互動式 Daemon 的工作集會因板型規格和 RAM 容量而有很大差異 (例如,SystemUI 在智慧手錶上使用約 45 MB,在 6 GB 手機上使用 60 MB,在 16 GB 手機上使用 180 MB),因此靜態的每裝置對照表並不穩定。相反地,SystemMemoryBudgetManager 會使用三個常數,直接從每個程序的常駐記憶體用量衍生出下限:
| 政策參數 | 預設值 | 行為 |
|---|---|---|
| 個別程序底價 | 90% 的已結算memory.current,扣除未對應的檔案快取 |
保護匿名堆積、slab 和熱對應程式碼,同時保留最冷 10% 的尾端可回收,因此仍可剔除真正冷的頁面。系統會先扣除未對應的檔案快取 (memory.stat 中的 max(0, file - shmem - file_mapped)),再套用 90% 的比率,因此啟動套件掃描快取或一次性資產讀取不會提高下限。 |
| 全域 RAM 上限 | RAM 總量的 10% (config_systemMemoryBudgetLowKb) |
符合PinnerService config_pinnerMaxPinnedMemoryPercentage護欄。如果 90% 底價的總和超過 MemTotal 的 10%,則每個底價都會按相同比例縮減。 |
| 重新整理頻率 | 啟動後 1、4、16 和 64 秒,之後每 30 分鐘一次 | 幾何穩定序列會收斂至每個 Daemon 啟動後的工作集,不需硬式編碼每部裝置的暖機延遲時間,而 30 分鐘的穩定狀態重新整理作業,則會追蹤裝置生命週期內工作集的逐漸增長和縮減。 |
在 Linux cgroup v2 記憶體控制器 (mm/memcontrol.c:effective_protection) 中,葉 cgroup 的有效 memory.min 保護受限於其父項和祖先 cgroup 上設定的 memory.min。如果只在葉 pid_<pid> 目錄上設定 memory.min,而 uid_<uid>/memory.min 為 0,則不會產生任何有效保護。相反地,如果只在頂層 /sys/fs/cgroup/system 切片上設定固定的 memory.min,系統會將未宣告的保護機制按比例分配給所有子 UID 和程序 (memory_recursiveprot),讓共用 uid_1000 (android.uid.system) 的診斷或次要程序從 surfaceflinger 和 audioserver 轉移保護機制。
為提供精確的個別 Daemon 隔離,SystemMemoryBudgetManager 會在每次重新整理時,在 cgroup v2 階層的所有三個層級寫入保護,如圖 1 所示:
- 葉處理程序節點 (
pid_<pid>/memory.min):設為處理程序的 (縮放) 90% 工作集下限。 - UID 節點 (
uid_<uid>/memory.min):設為其受管理pid_*葉節點底限的確切總和,因此共用該 UID 的未受管理程序 (例如其他AID_SYSTEMdaemon 或sharedUserId="android.uid.system"套件) 具有memory.min = 0,且無法要求保護。 - 頂層切片 (
/sys/fs/cgroup/system/memory.min和/sys/fs/cgroup/apps/memory.min):設為其uid_*子項的確切總和。受管理節點上的任何過時memory.low值都會清除為0,且在先前重新整理時管理的任何uid_*目錄 (例如使用者切換啟動器後的先前主畫面啟動器) 都會重設為0。
圖 1. cgroup 切片的三層保護底限委派。
為何在 MGLRU 核心上使用 memory.min 而非 memory.low
在多世代 LRU (MGLRU,Android 6.6 以上版本核心的預設頁面回收引擎) 下,memory.low 和 memory.min 在 mm/vmscan.c:shrink_one 中的行為不同:
memory.low(單次延遲):用量低於memory.low的 cgroup 會輪替至 memcg LRU (MEMCG_LRU_TAIL) 的尾端,並在單次回收中略過。在記憶體壓力持續的情況下,核心會在下次傳遞時完整回收 cgroup,並記錄MEMCG_LOW事件。在實體裝置壓力測試中,匿名記憶體佔用量增加至實體 RAM 的 85%,memory.low允許智慧手錶上的SystemUI從約 45 MB 縮減至約 5 到 10 MB,而 16 GB 摺疊式裝置上的audioserver則從 15 MB 降至 2 MB,而下限約為 14 MB。memory.min(硬性保護下限):全域回收會略過用量低於memory.min的 cgroup。如圖 2 所示,在相同的 85% RAM 壓力工作負載下,memory.min只會捨棄每個 Daemon 最不常使用的 10% 尾端,並將SystemUI、surfaceflinger和audioserver保持在 90% 的下限,不會發生匿名重新錯誤,也不會增加lmkd程序終止。
memory.min 提供硬體實體保留,與其取代的 PinnerService mlock 固定保護相符,其優點是核心 LRU (而非靜態檔案清單) 會選擇哪些熱程式碼、堆積和圖形頁面保持常駐。
圖 2. 記憶體壓力上升時,Daemon 工作組保留。
開機預熱和開機後未對應檔案快取回收
如要取代 config_defaultPinnerServiceFiles 的 PinnerService 永久 mlock,必須協調兩種開機時行為:
- 單次背景啟動預熱 (
PinnerService):略過PinnerServicemlock而不預熱頁面快取,會在SystemUI啟動時 (OnBootPhase_550),將約 95 MB 的SystemUIAPK、ODEX 和 VDEX 磁碟讀取作業轉移至同步啟動路徑,導致啟動完成時間增加 300 至 450 毫秒。呼叫madvise(MADV_WILLNEED)是不夠的,因為核心中的force_page_cache_ra會將單一MADV_WILLNEED呼叫限制在一個預讀視窗 (通常為 128 KB 至 2 MB)。相反地,當SystemMemoryBudgetManager.shouldSkipDefaultSystemPinning處於啟用狀態時,PinnerService會在onStart期間,於背景執行緒上對應config_defaultPinnerServiceFiles中的每個檔案,使用Os.mlock將檔案同步填入頁面快取,並立即呼叫Os.munlock、munmap和close。這樣一來,系統就能維持快速啟動時間,同時不留下任何永久鎖定的位元組,以便日後移除冷啟動頁面。 - 排除並回收
system_server啟動套件掃描快取:啟動期間,PackageManagerService會掃描已安裝的 APK,並將 1.5 到 2.5 GB 的冷未對應檔案頁面快取,計入system_servercgroup。如果計入system_server的memory.min計算,冷啟動快取會達到 10% 的全域 RAM 上限,並按比例縮減surfaceflinger、SystemUI和audioserver的保護下限。為避免上限不足,SystemMemoryBudgetManager會讀取memory.stat的system_server,將未對應的檔案快取計算為max(0, file - shmem - file_mapped),並在計算 90% 下限前,從memory.current減去未對應的快取,如圖 3 所示。此外,在PHASE_BOOT_COMPLETED之後 10 秒,管理員會對 Linux 6.2 和更新版本核心上的system_server的memory.reclaim節點執行<unmapped_file> swappiness=0的一次性寫入,主動清除冷套件掃描快取,而不交換匿名堆積或清除對應的 DEX 和程式碼頁面。在拒絕swappiness=0和EINVAL的舊核心 (例如android14-6.1) 上,管理員會略過memory.reclaim寫入,而不是改用不合格的回收寫入,這會在啟動期間換出匿名堆積。
圖 3. 啟動後,保護下限會收斂,並回收快取。
核心需求和裝置設定疊加層
SystemMemoryBudgetManager 依賴 Linux 核心中的 mem_cgroup_calculate_protection,在巢狀 cgroup 之間傳播 memory.min。Android 通用核心 android15-6.6 和 android16-6.12 內建完整的 MGLRU 巢狀保護支援。搭載 android14-6.1 且啟用 MGLRU 的裝置,需要在 mm/vmscan.c 和 mm/memcontrol.c 中進行三個上游向後移植,以便空白的中間切片和 UID cgroup 將保護機制傳播至分葉程序 cgroup:
30d77b7eef01(mm/mglru: fix ineffective protection calculation)8aa420617918(mm/mglru: respect min_ttl_ms with memcgs)adb8213014b2(mm: memcg: fix stale protection of reclaim target memcg)
原始設備製造商 (OEM) 可以使用下列架構資源疊加層,自訂或停用 SystemMemoryBudgetManager:
config_systemMemoryBudgetLowKb(整數,KB):0(預設) 將所有memory.min下限的總和限制為實體 RAM 總量的 10%。正值會以 KB 為單位設定明確上限,負值則會停用裝置上的SystemMemoryBudgetManager(保留舊版PinnerService靜態mlock固定)。config_systemMemoryBudgetHighKb(整數,KB):0(預設) 會將/sys/fs/cgroup/system/memory.high保持在max。正值會設定頂層切片memory.high上限 (必須大於或等於下限)。config_systemMemoryBudgetAdditionalSystemProcesses(字串陣列):列出/sys/fs/cgroup/system中其他對延遲時間敏感的原生系統或供應商 daemon 程序名稱 (與/proc/<pid>/comm或/proc/<pid>/cmdline中argv[0]的基本名稱比對,適用於10000下的系統和供應商 UID),以 90%memory.min下限保護,以及預設的六個系統 daemon。請使用這個疊加層,而不是在init.rc中設定memory.min,因為SystemMemoryBudgetManager會將uid_<uid>/memory.min重新計算為其代管分葉程序的總和,並在每次重新整理時清除未代管的uid_*節點。舉例來說,XR 裝置可以使用下列疊加層,保護空間追蹤和相機 HAL 守護程序:<string-array translatable="false" name="config_systemMemoryBudgetAdditionalSystemProcesses"> <item>android.hardware.perceptionhost</item> <item>auracamerahal</item> </string-array>config_systemUi(字串):平台 SystemUI 應用程式的套件名稱,使用MATCH_SYSTEM_ONLY解析。
如要檢查裝置上有效的 SystemMemoryBudgetManager 樓層和 cgroup 節點,請執行:
# Inspect policy state, resolved PIDs, and applied floors in dumpsys meminfo adb shell dumpsys meminfo | sed -n '/System Memory Budgets/,$p'# Inspect raw cgroup v2 memory.min nodes across the system and apps slices adb shell head -n 1 \ /sys/fs/cgroup/system/memory.min \ /sys/fs/cgroup/system/uid_*/memory.min \ /sys/fs/cgroup/system/uid_*/pid_*/memory.min \ /sys/fs/cgroup/apps/memory.min \ /sys/fs/cgroup/apps/uid_*/memory.min \ /sys/fs/cgroup/apps/uid_*/pid_*/memory.min
使用記憶體預算限制永久和背景系統應用程式
雖然 SystemMemoryBudgetManager 會使用 memory.min 下限保護互動式 Daemon 免於回收,但許多其他系統應用程式和特殊權限平台服務會長時間在背景執行,例如 PERSISTENT 程序、繫結前景服務或經常查詢的內容供應器。如果沒有明確的記憶體上限,這些程序就不會受到限制,直到達到數 GB 的記憶體限制器上限 (例如可見應用程式為 4 GB 至 10 GB,背景應用程式為 2 GB 至 5 GB),或全域記憶體壓力觸發全系統 kswapd 和低記憶體終止程式 (lmkd) 活動。
如果不加以限制,背景系統程序通常會累積大量冷記憶體,這些記憶體不屬於其活動工作集:
- 無限制檔案頁面快取:為媒體建立索引、搜尋資料庫或同步處理日曆和供應商資料表的服務 (例如
AppSearch、MediaProvider和CalendarProvider),會透過磁碟上的大型檔案進行對應或串流。如果沒有 cgroupmemory.high上限,一次性讀取作業產生的乾淨檔案備份頁面會無限期地向程序 cgroup 收費,消耗數百 MB 的實體 RAM。 - 突發後匿名堆積保留:如果服務會出現短暫的活動高峰,例如
Bluetooth音訊和 BLE 探索突發、NetworkStack網頁認證入口 TLS 驗證,或 UI 輔助程式轉換至背景,則可能會在突發完成後,長時間保留冷匿名堆積頁面。
將校正後的 <memory-budget> 宣告 (memory.high 軟限制,通常為 30 MB 至 190 MB) 套用至系統應用程式,可帶來兩大好處:
- 主動、局部頁面回收:當系統程序超過設定的
memory.high預算時,Linux 核心會直接針對該 cgroup 進行頁面回收,優先移除冷檔案支援頁面,並將不活躍的匿名頁面壓縮至 ZRAM,而不會等待全域裝置記憶體壓力。 - 前景應用程式和快取應用程式集區有更多可用 RAM:在全天候運作系統服務中繫結冷檔案快取,可回收裝置上數十到數百 MB 的實體 RAM,減少 UI 卡頓、降低相機啟動壓力,並讓更多使用者應用程式保持在快取狀態。
設定系統應用程式記憶體預算
系統應用程式與一般應用程式使用相同的 <memory-budget> 資訊清單語法、程序狀態串聯、螢幕密度縮放和最後比對子句解析規則。如需完整的屬性清單,請參閱 XML 屬性參考資料。
原始設備製造商 (OEM) 可以直接在系統應用程式的 AndroidManifest.xml 中宣告 <memory-budget> 元素,或使用 Android.bp 中的 additional_manifests 屬性,從獨立 XML 檔案合併這些元素。按照慣例,AOSP 會將參考系統應用程式預算儲存在獨立的 *-budget.xml 檔案中 (位於核心平台套件的 frameworks/base/core/memory_budgets/ 中,或與個別模組 (例如 packages/modules/NetworkStack/memory_budgets/、packages/modules/AppSearch/apk/ 和 packages/services/Telephony/) 一起儲存),並使用 additional_manifests 附加這些檔案:
android_app {
name: "TeleService",
manifest: "AndroidManifest.xml",
additional_manifests: ["Telephony-budget.xml"],
// ...
}
套件層級 (UID) 與程序層級 (PID) 範圍
您宣告的是套件層級基準預算 (<application>) 還是多程序預算 (<processes><process>),會決定核心是在 UID cgroup 層級 (/sys/fs/cgroup/uid_<uid>/memory.high) 還是個別 PID cgroup 層級 (/sys/fs/cgroup/uid_<uid>/pid_<pid>/memory.high) 強制執行 memory.high,如圖 4 所示:
- 套件層級 (
<application>) 預算:如果 APK 在專屬 UID 下執行單一程序,請使用此預算 (例如Bluetooth、Nfc、NetworkStack、CalendarProvider、Traceur或AppSearch)。 - 程序層級 (
<processes><process>) 預算:如果 APK 與其他系統套件共用android:sharedUserId(例如 UID 為1001的android.uid.phone或 UID 為1000的android.uid.system),請使用此預算,這樣共用該 UID 的其他程序就不會一起受到限制;或者,如果單一 APK 執行多個具有不同記憶體角色的程序 (例如背景MediaProvider守護程序和:PhotoPickerUI 子程序),也請使用此預算。
圖 4. UID 層級與 PID 層級的記憶體上限。
以下範例顯示 MediaProvider-budget.xml,將 RAM 分級預算範圍限定為預設 MediaProvider 程序 (<process>),並將顯示比例預算限定為 :PhotoPicker UI 程序。每個 android:maxDeviceRamMb 屬性都會比對實體 RAM 總量 (MemTotal) 等於或低於指定 MB 門檻的裝置 (如省略,則適用於任何 RAM 容量)。子句的排序方式是從不受限制的預設值,到最小的 maxDeviceRamMb 門檻,因此最後比對子句解析規則會選取最嚴格的適用層級:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.providers.media.module">
<uses-sdk android:targetSdkVersion="10000" />
<application>
<processes>
<process>
<memory-budget
android:maxMb="85" />
<memory-budget
android:maxMb="75"
android:maxDeviceRamMb="12288" />
<memory-budget
android:maxMb="60"
android:maxDeviceRamMb="8192" />
</process>
<process android:process=":PhotoPicker">
<memory-budget
android:maxMb="85"
android:additionalBytesPerDisplayPixel="25" />
</process>
</processes>
</application>
</manifest>
如何在 Google Pixel 裝置上校正 AOSP 參考預算
AOSP 中的參考系統應用程式記憶體預算,是根據 Google Pixel 裝置校準,涵蓋 6 GB 至 16 GB 的實體 RAM (包括 6 GB、8 GB、12 GB 和 16 GB 的手機、折疊式裝置和平板電腦),以及約 1.2 MP (720×1600) 至約 4.5 MP (2076×2152) 的螢幕解析度。
校正作業結合了兩個互補的資料來源:整個機群的 cgroup v2 欄位遙測資料,以及在關鍵使用者歷程 (CUJ) 中受控的實體裝置 memory.high 掃描。
全機群 cgroup v2 遙測分析
系統會定期收集整個裝置群的 cgroup v2 快照,並依實體 RAM 級別 (≤8 GB、12 GB 和 ≥16 GB) 和程序狀態值區 (foreground、perceptible、background 和 cached) 進行切片。統計分析遵循六項原則:
- 將
memory.current分解為工作集和可回收檔案快取:由於memory.high控管常駐 cgroup 費用 (memory.current),因此每個快照都會使用memory.stat分解為匿名堆積 (anon)、交換匿名頁面 (swap) 和檔案支援頁面快取 (file),如圖 5 所示。不可壓縮的匿名需求總量 (P99anon + swap) 會建立預算的最小工作集下限,因此程序不會有 ZRAM 耗盡的風險,而memory.currentP99 和anon + swapP99 之間的巨大差距表示memory.high可回收的檔案頁面快取,前提是快取未對應且冷。 - 考量
memory.high無法回收的記憶體:並非所有檔案和共用記憶體都是冷快取。鎖定的頁面 (memory.stat中的unevictable,例如使用mlock鎖定的共用記憶體,或虛擬機器鎖定的客體 RAM,其主機程序會向應用程式的 cgroup 收費) 會在預算下設定硬性下限,而對應頁面 (file_mapped) 通常是有效工作集的一部分。請先檢查unevictable、shmem和file_mapped,再將檔案記憶體視為可回收。舉例來說,約 100 MB 的AppSearch記憶體會鎖定且無法移除,因此其預算會涵蓋該下限加上其熱主機工作集,並為鎖定區域保留成長空間。 - 篩除閒置狀態稀釋:內容供應器 (
CalendarProvider) 和隨選開發人員工具 (Traceur) 在 70% 到 99.9% 的現場快照中,閒置時的記憶體用量約為 1 MB 到 3 MB。如果計算所有快照的原始百分位數,會嚴重稀釋 P95 和 P99 預估值。而是根據未稀釋的活躍快照 (篩除CACHED快照和閒置基準快照),計算百分位數和信賴區間。 - 使用裝置分群信賴區間:由於單一裝置會隨著時間提供多個定期快照,因此將快照視為獨立樣本會低估尾端不確定性。所有 P95 和 P99 工作集預估值,都是使用保守的裝置分群 95% 信賴區間 (以不重複裝置數量
N_dev做為有效樣本大小) 評估。 - 使用
memory.peak處理暫時性爆量:定期取樣可能會遺漏短暫、高分配的爆量 (例如NetworkStack網頁認證入口 TLS 驗證和並行 DNS 探測,或BluetoothA2DP 和 BLE 初始化)。對於突發性服務,預算大小應涵蓋 RAM 級別中核心memory.peak存留期高水位標記的第 90 到 95 個百分位數。 - 確認 RAM 級別反映的是 RAM,而非裝置世代:較新的裝置往往有更多 RAM 和功能,因此隨著 RAM 級別增加的工作集,實際上可能會隨著功能增加。新增
maxDeviceRamMb級別前,請先依裝置型號劃分資料。舉例來說,在同世代的 12 GB 和 16 GB 手機上,com.android.phone達到相同的 P99anon + swap(約 85 至 90 MB),而舊款 12 GB 摺疊式手機則使用 40 MB。因此,其預算使用單一值,沒有 RAM 級別,因此搭載較新電話功能的低 RAM 裝置不會預算不足。
圖 5. 工作集與可回收檔案快取。
實體裝置 CUJ 和顯示比例掃描
為了驗證遙測資料得出的預算,並校正與螢幕成比例的預算 (例如 :PhotoPicker) 和特定狀態的背景預算 (例如 Traceur),我們在 RAM 介於 6 GB 到 16 GB 的實體裝置上,執行了即時 memory.high 清除作業:
- 找出零重新錯誤膝點:在執行端對端 CUJ (例如快速相片格線滑動、相簿瀏覽、全螢幕高解析度相片和影片預覽滑動,以及追蹤記錄和儲存) 時,
memory.high會向下步進,同時測量 cgroupmemory.stat計數器 (workingset_refault_anon、workingset_refault_file、pgscan和pgsteal) 和memory.events(high)。如圖 6 所示,最佳預算剛好落在零重新錯誤膝點上方。零重新錯誤膝點是核心回收冷一次性檔案快取 (pgsteal > 0) 的門檻,不會觸發主動工作集抖動 (workingset_refault == 0)。 - 驗證不同解析度的顯示像素縮放:在四種實體和模擬顯示解析度
:PhotoPicker(從約 1.2 MP (720×1600) 到約 4.5 MP (2076×2152 展開內螢幕)) 進行測試,確認其作用中的 Glide 和 ViewPager2 點陣圖工作集會隨著顯示像素數量線性縮放。非顯示基準也會因裝置而異:在像素數量幾乎相同的情況下,16 GB 折疊式裝置外螢幕的零重新錯誤拐點約為 140 MB,而 6 GB 手機則為 110 至 125 MB。85 MB 基準加上每個顯示像素 25 位元組 (android:maxMb="85"和android:additionalBytesPerDisplayPixel="25"),在每個測試顯示器上都高於測得的膝點 (HD+ 約 110 MB,FHD+ 約 145 MB,展開顯示器約 190 MB)。 - 驗證背景狀態下的 ART 垃圾收集:在 cgroup v2 中,Zygote 共用的
boot.art和架構映像檔頁面仍會計入 Zygote 的 cgroup,並在 ART 並行標記壓縮 (CMC) 垃圾收集期間視為免疫空間。壓力測試證實,當系統 UI 應用程式轉換至background時,收緊memory.high(例如在Traceur上設為 30 MB) 可明確限制私人堆積和頁面快取,而不會造成 GC 顛簸或交換活動。
圖 6. 在 CUJ 期間找出零重新錯誤膝點。
AOSP 參考系統應用程式預算摘要
下表匯總 AOSP 中各 RAM 級別和程序狀態的校正參考預算:
| 系統套件或程序 | 範圍 | ≤8 GB RAM | 12 GB RAM | ≥16 GB RAM | 主要校正驅動程式 |
|---|---|---|---|---|---|
com.android.bluetooth |
UID (<application>) |
80 MB | 95 MB | 110 MB | 涵蓋 P90 存留期 memory.peak 爆發 (約 75 至 110 MB) 和 >2x P99 活躍 memory.current。 |
com.android.nfc |
UID (<application>) |
50 MB | 60 MB | 60 MB | 涵蓋有效主機卡片模擬 P99 (最多約 50 MB) 和 P90 至 P95 memory.peak,同時修剪冷檔案快取。 |
com.android.providers.calendar |
UID (<application>) |
預設 70 MB (前景) 50 MB perceptible 和 background |
有效工作集在各個 RAM 等級中皆不變。背景預算會保留匿名餘裕,用於提醒鬧鐘,同時在同步處理期間修剪 SQLite 頁面快取。 | ||
com.android.traceur |
UID (<application>) |
預設 85 MB (前景和可感知) 30 MB background |
在主動追蹤記錄和匯出期間 (TraceService),保留完整的 85 MB 預留空間,同時將閒置和廣播狀態限制在 30 MB。 |
||
com.android.providers.media.module (主要) |
PID (<process>) |
60 MB | 75 MB | 85 MB | 提供約 10 到 30 MB 的 P99 anon + swap 預留空間,同時回收冷 SQLite 和縮圖檔案快取。 |
com.android.providers.media.module:PhotoPicker |
PID (<process>) |
85 MB + 25 位元組/像素 (HD+ 約 110 MB,FHD+ 約 145 MB,~4.5 MP 摺疊式裝置約 190 MB) |
與螢幕像素數量 (additionalBytesPerDisplayPixel="25") 成比例縮放,以符合 Glide 點陣圖工作集膝點。 |
||
com.android.networkstack |
UID (<application>) |
65 MB | 70 MB | 85 MB | 主要為匿名記憶體,大小為 P95 存留時間 memory.peak,可涵蓋網頁認證入口 TLS、DNS 和 DHCP 突發流量。 |
com.android.appsearch.apk |
UID (<application>) |
130 MB | 涵蓋約 100 MB 的鎖定、不可移除記憶體,以及約 5 MB 的熱主機工作集,並預留鎖定區域成長空間。在冷頁快取 (P99 時最多約 330 MB) 回收時,測得的零重新錯誤值 ≥125 MB。 | ||
com.android.phone (TeleService) |
PID (<process>) |
120 MB | 共用 UID 1001 的每個 PID 範圍。工作集會追蹤裝置世代,而非 RAM,因此單一預算會比每個模型最高的 P99 anon + swap 高出約 30 MB。 |
||
原始設備製造商 (OEM) 如何重新校準自家裝置的系統應用程式預算
如果原始設備製造商 (OEM) 自訂系統應用程式 (例如新增供應商藍牙設定檔、自訂電話通訊擴充功能或 OEM 媒體索引器),或提供額外的預先安裝系統服務,則可重新校準現有預算,或為其硬體產品組合編寫新的 *-budget.xml 檔案。
收集使用中的 cgroup v2 記憶體分析器
針對每個目標系統程序,在代表性工作負載和 RAM 級別中,讀取 /sys/fs/cgroup/uid_<uid>/pid_<pid>/ 下的 cgroup v2 記憶體節點:
memory.current:目前向 cgroup 收取的常駐記憶體 (不含 Zygote 共用頁面)memory.peak:自程序啟動以來,生命週期內memory.current的最高用量memory.swap.current:ZRAM 目前的壓縮交換空間用量memory.stat:anon、file、pgscan、pgsteal、workingset_refault_anon和workingset_refault_file的細目memory.events:high違規事件 (memory.high節流和回收事件) 的累計次數
從 dogfood 或現場裝置匯總遙測資料時,請將作用中程序狀態 (TOP、FOREGROUND_SERVICE、作用中繫結和作用中同步) 與閒置 CACHED 或閒置 PERSISTENT 狀態分開,以免閒置快照導致 P95 和 P99 工作集計算結果偏低。
將不可壓縮的堆積與可回收的檔案快取分開
比較 anon + swap 的第 99 個百分位數與 memory.current 的第 99 個百分位數:
- 檔案快取主導的服務 (
file≫anon,例如資料庫供應商、搜尋索引器和記錄常駐程式):設定android:maxMb,提供比 P99anon + swap更充裕的空間 (通常為 1.5 到 4 倍),以及熱門檔案頁面的工作視窗。這項設定可讓核心持續剔除冷未對應的檔案頁面,而不觸及匿名堆積。如果unevictable或file_mapped很大 (例如鎖定記憶體或記憶體對應資料庫),請將該足跡也計入工作集。 - 匿名堆積主導或突發服務 (
anon占memory.current的大部分,例如網路或連線能力堆疊):調整android:maxMb的大小,涵蓋memory.peak的 P90 至 P95,這樣暫時性突發就不會在延遲敏感執行緒上觸發同步 ZRAM 交換。 - 顯示密集型 UI 程序 (例如挑選器、圖片庫或系統 UI 輔助程式):測量最低和最高解析度螢幕的基準非 UI 占用空間和每百萬像素的增量點陣圖占用空間,並使用
android:maxMb加上android:additionalBytesPerDisplayPixel表示預算。
在實體裝置上清除 memory.high
在建構作業中採用新的預算前,請使用 am memory-limiter manual (或在 userdebug 建構作業中直接寫入程序的 memory.high cgroup 節點),在執行自動化 CUJ 測試時測試候選限制:
# Override memory.high for a specific PID to 60 MB during a CUJ test
adb shell am memory-limiter manual <PID> 60MB在 CUJ 前後監控 memory.stat 和 memory.events:
- 預算過低:
workingset_refault_anon或workingset_refault_file在 CUJ 期間急遽上升、memory.swap.current在前景工作期間持續增加,或 CUJ 影格或繫結器延遲時間倒退。將預算提高至超過預設轉折點。 - 預算過高:即使在大量檔案串流或索引工作負載之後,
memory.events:high仍維持0,且memory.stat中的file會隨著冷頁面無限成長。縮減預算,將資源集中在有效工作集。 - 預算經過妥善校正:正常運作期間,穩態
memory.current會維持在memory.high以下。在檔案或快取大量爆發期間,pgsteal會回收冷檔案頁面,而workingset_refault_anon則維持在接近零,且 CUJ 延遲時間不變。