讀書筆記,如需轉載,請註明作者:Yuloran (t.cn/EGU6c76)
前言
筆者在之前的文章《分析並優化 Android 應用記憶體佔用》中提到,為了避免 Cached Pages 太少時導致裝置卡頓、當機、重啟等情況,Android 引入了 LowMemoryKiller(源自 Linux OOM Killer) 機制,提前回收優先順序比較低的程式所佔的資源,以保證一個較好的使用者體驗。程式優先順序列表由 SystemServer 程式維護:
其中 Cached 程式列表,使用的是 LRU 演算法。
每個 Java 程式都有一個相關聯的 ProcessRecord 物件,其成員變數 curAdj 就表示該程式當前狀態下的優先順序:
int curAdj; // Current OOM adjustment for this process
複製程式碼
當裝置可用記憶體低於 LMK 閾值時,LMK 便會根據程式的優先順序,逐級殺死程式並釋放其佔用的資源。
建議先閱讀筆者前四篇博文,以對 Android 記憶體相關有一個比較全面而深入的瞭解:
本篇博文為 Android 記憶體系列的最後一篇。
程式的生命週期
本小節摘自 Google Android Developers 《程式和執行緒》,LMK 殺死程式時,將遵從 空程式 -> 後臺程式 -> 服務程式 -> 可見程式 -> 前臺程式
的順序:
前臺程式
使用者當前操作所必需的程式。如果一個程式滿足以下任一條件,即視為前臺程式:
- 託管使用者正在互動的 Activity(已呼叫 Activity 的 onResume() 方法)
- 託管某個 Service,該 Service 繫結到使用者正在互動的 Activity
- 託管正在“前臺”執行的 Service(服務已呼叫 startForeground())
- 託管正執行一個生命週期回撥的 Service(onCreate()、onStart() 或 onDestroy())
- 託管正執行其 onReceive() 方法的 BroadcastReceiver
通常,在任意給定時間前臺程式都為數不多。只有在記憶體不足以支援它們同時繼續執行這一萬不得已的情況下,系統才會終止它們。
可見程式
沒有任何前臺元件、但仍會影響使用者在螢幕上所見內容的程式。 如果一個程式滿足以下任一條件,即視為可見程式:
- 託管不在前臺、但仍對使用者可見的 Activity(已呼叫其 onPause() 方法)。例如,如果前臺 Activity 啟動了一個對話方塊,則有可能會發生這種情況。
- 託管繫結到可見(或前臺)Activity 的 Service。
可見程式被視為是極其重要的程式,除非為了維持所有前臺程式同時執行而必須終止,否則系統不會終止這些程式。
服務程式
正在執行已使用 startService() 方法啟動的服務且不屬於上述兩個更高類別程式的程式。
儘管服務程式與使用者所見內容沒有直接關聯,但是它們通常在執行一些使用者關心的操作(例如,在後臺播放音樂或從網路下載資料)。因此,除非記憶體不足以維持所有前臺程式和可見程式同時執行,否則系統會讓服務程式保持執行狀態。
後臺程式
包含目前對使用者不可見的 Activity 的程式(已呼叫 Activity 的 onStop() 方法)。
這些程式對使用者體驗沒有直接影響,系統可能隨時終止它們,以回收記憶體供前臺程式、可見程式或服務程式使用。 通常會有很多後臺程式在執行,因此它們會儲存在 LRU (最近最少使用)列表中,以確保包含使用者最近檢視的 Activity 的程式最後一個被終止。
空程式
不含任何活動應用元件的程式。
保留這種程式的的唯一目的是用作快取,以縮短下次在其中執行元件所需的啟動時間。 為使總體系統資源在程式快取和底層核心快取之間保持平衡,系統往往會終止這些程式。
LowMemoryKiller
Andorid 的 Low Memory Killer 是在標準的 Linux Kernel 的 OOM Killer 基礎上修改而來的一種記憶體管理機制。當系統記憶體不足時,殺死不重要的程式以釋放其記憶體。LMK 的關鍵引數有 3 個:
- oom_adj:在 Framework 層使用,代表程式的優先順序,數值越高,優先順序越低,越容易被殺死。
- oom_adj threshold:在 Framework 層使用,代表 oom_adj 的記憶體閾值。Android Kernel 會定時檢測當前剩餘記憶體是否低於這個閥值,若低於則殺死 oom_adj ≥ 該閾值對應的 oom_adj 中,數值最大的程式,直到剩餘記憶體恢復至高於該閥值的狀態。
- oom_score_adj: 在 Kernel 層使用,由 oom_adj 換算而來,是殺死程式時實際使用的引數。
oom_adj
取值範圍定義:
-> ProcessList(AOSP, master 分支)
// These are the various interesting memory levels that we will give to
// the OOM killer. Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
複製程式碼
以上僅是 LMK 殺死程式時使用的 adj,實際上該類中定義了更多的 adj:
常量定義 | 常量取值 | 含義 |
---|---|---|
NATIVE_ADJ | -1000 | native程式(不被系統管理) |
SYSTEM_ADJ | -900 | 系統程式 |
PERSISTENT_PROC_ADJ | -800 | 系統persistent程式,比如telephony |
PERSISTENT_SERVICE_ADJ | -700 | 關聯著系統或persistent程式 |
FOREGROUND_APP_ADJ | 0 | 前臺程式 |
VISIBLE_APP_ADJ | 100 | 可見程式 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知程式,比如後臺音樂播放 |
BACKUP_APP_ADJ | 300 | 備份程式 |
HEAVY_WEIGHT_APP_ADJ | 400 | 後臺的重量級程式,system/rootdir/init.rc檔案中設定 |
SERVICE_ADJ | 500 | 服務程式 |
HOME_APP_ADJ | 600 | Home程式 |
PREVIOUS_APP_ADJ | 700 | 上一個App的程式 |
SERVICE_B_ADJ | 800 | B List中的Service(較老的、使用可能性更小) |
CACHED_APP_MIN_ADJ | 900 | 不可見程式的adj最小值 |
CACHED_APP_MAX_ADJ | 906 | 不可見程式的adj最大值 |
UNKNOWN_ADJ | 1001 | 一般指將要會快取程式,無法獲取確定值 |
以上常量在 Android 6.0(API23)及之前版本的取值範圍為 [-17, 16]:ProcessList(AOSP,marshmallow-release 分支)
規律:取值越大,重要性越低,程式越容易被殺死。
當觸發 LowMemoryKiller 機制時,可根據日誌中程式的 adj 值,具體分析程式是在什麼狀態下被殺死的。
oom_adj threshold
取值範圍定義:
-> ProcessList(AOSP, master 分支)
// The actual OOM killer memory levels we are using.
private final int[] mOomMinFree = new int[mOomAdj.length];
複製程式碼
具體取值由下面兩個變數經過換算得到:
// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
12288, 18432, 24576,
36864, 43008, 49152
};
// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
73728, 92160, 110592,
129024, 147456, 184320
};
複製程式碼
陣列初始化或更新的方法:
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
int minSize = 480*800; // 384000
int maxSize = 1280*800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
...省略
final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
...省略
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i++) {
// 除以了 PAGE_SIZE,所以 minfree 中的單位為頁,及 4KB
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
// 向 lmkd 程式傳送 LMK_TARGET 命令,
// 將 oom_adj 閾值寫入 "/sys/module/lowmemorykiller/parameters/minfree"
// 將 oom_adj 寫入 "/sys/module/lowmemorykiller/parameters/adj"
writeLmkd(buf);
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
複製程式碼
在 ActivityManagerService 呼叫 updateConfiguration() 的過程中會呼叫該方法,根據裝置的解析度初始化或更新閾值的大小。
oom_score_adj
取值範圍定義在 Linux Kernel 中:
-> oom.h
#ifndef _UAPI__INCLUDE_LINUX_OOM_H
#define _UAPI__INCLUDE_LINUX_OOM_H
/*
* /proc/<pid>/oom_score_adj set to OOM_SCORE_ADJ_MIN disables oom killing for
* pid.
*/
#define OOM_SCORE_ADJ_MIN (-1000)
#define OOM_SCORE_ADJ_MAX 1000
/*
* /proc/<pid>/oom_adj set to -17 protects from the oom killer for legacy
* purposes.
*/
#define OOM_DISABLE (-17)
/* inclusive */
#define OOM_ADJUST_MIN (-16)
#define OOM_ADJUST_MAX 15
#endif /* _UAPI__INCLUDE_LINUX_OOM_H */
複製程式碼
oom_adj 到 oom_score_adj 的換算方法定義在 Linux Driver 中:
static int lowmem_oom_adj_to_oom_score_adj(int oom_adj)
{
if (oom_adj == OOM_ADJUST_MAX)
return OOM_SCORE_ADJ_MAX;
else
return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
}
複製程式碼
工作流程
簡述 LMK 的工作流程
啟動 lmkd
lmkd 是由 init程式通過解析 init.rc 檔案來啟動的守護程式。lmkd 會建立名為 lmkd 的 socket,節點位於 /dev/socket/lmkd,該 socket 用於跟上層 framework 互動:
service lmkd /system/bin/lmkd
class core
group root readproc
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
複製程式碼
lmkd 啟動後,便會進入迴圈等待狀態,接受來自 ProcessList 的三個命令:
命令 | 功能 | 方法 |
---|---|---|
LMK_TARGET | 初始化 oom_adj | ProcessList::setOomAdj() |
LMK_PROCPRIO | 更新 oom_adj | ProcessList::updateOomLevels() |
LMK_PROCREMOVE | 移除程式(暫時無用) | ProcessList::remove() |
初始化 oom_adj
在 ActivityManagerService 呼叫 updateConfiguration() 的過程中會呼叫 ProcessList::updateOomLevels() 方法,根據裝置的解析度調整閾值的大小,通過 LMK_TARGET 命令,通知 lmkd(low memory killer deamon)分別向 /sys/module/lowmemorykiller/parameters 目錄下的 minfree 和 adj 節點寫入相應資訊:
更新 oom_adj
在 ActivityManagerService 呼叫 applyOomAdjLocked() 的過程中會呼叫 ProcessList::setOomAdj() 方法,通過 LMK_PROCPRIO 命令,通知 lmkd 向 /proc/程式號/oom_score_adj 寫入 oomadj:
D:\Android\projects\wanandroid_java>adb shell ps | findstr wanandroid
u0_a71 6461 1285 1177696 84024 SyS_epoll_ b131e424 S com.yuloran.wanandroid_java
D:\Android\projects\wanandroid_java>adb shell ls /proc/6461/
...省略
oom_adj
oom_score
oom_score_adj
...
複製程式碼
ActivityManagerService 會根據當前應用程式託管元件(即四大元件)生命週期的變化,及時的呼叫 applyOomAdjLocked(),更新程式狀態及該狀態對應的 oom_adj。
程式狀態表:
-> ActivityManager.java(AOSP,branch:master)
常量定義 | 常量取值 | 含義 |
---|---|---|
PROCESS_STATE_UNKNOWN | -1 | 非真實的程式狀態 |
PROCESS_STATE_PERSISTENT | 0 | persistent 系統程式 |
PROCESS_STATE_PERSISTENT_UI | 1 | persistent 系統程式,並正在執行UI操作 |
PROCESS_STATE_TOP | 2 | 擁有當前使用者可見的 top Activity |
PROCESS_STATE_FOREGROUND_SERVICE | 3 | 託管一個前臺 Service 的程式 |
PROCESS_STATE_BOUND_FOREGROUND_SERVICE | 4 | 託管一個由系統繫結的前臺 Service 的程式 |
PROCESS_STATE_IMPORTANT_FOREGROUND | 5 | 對使用者很重要的程式,使用者可感知其存在 |
PROCESS_STATE_IMPORTANT_BACKGROUND | 6 | 對使用者很重要的程式,使用者不可感知其存在 |
PROCESS_STATE_TRANSIENT_BACKGROUND | 7 | Process is in the background transient so we will try to keep running. |
PROCESS_STATE_BACKUP | 8 | 後臺程式,正在執行backup/restore操作 |
PROCESS_STATE_SERVICE | 9 | 後臺程式,且正在執行service |
PROCESS_STATE_RECEIVER | 10 | 後臺程式,且正在執行receiver |
PROCESS_STATE_TOP_SLEEPING | 11 | 與 PROCESS_STATE_TOP 一樣,但此時裝置正處於休眠狀態 |
PROCESS_STATE_HEAVY_WEIGHT | 12 | 後臺程式,但無法執行restore,因此儘量避免kill該程式 |
PROCESS_STATE_HOME | 13 | 後臺程式,且擁有 home Activity |
PROCESS_STATE_LAST_ACTIVITY | 14 | 後臺程式,且擁有上一次顯示的 Activity |
PROCESS_STATE_CACHED_ACTIVITY | 15 | 程式處於 cached 狀態,且內含 Activity |
PROCESS_STATE_CACHED_ACTIVITY_CLIENT | 16 | 程式處於 cached 狀態,且為另一個 cached 程式(內含 Activity)的 client 程式 |
PROCESS_STATE_CACHED_RECENT | 17 | 程式處於 cached 狀態,且內含與當前最近任務相對應的 Activity |
PROCESS_STATE_CACHED_EMPTY | 18 | 程式處於 cached 狀態,且為空程式 |
PROCESS_STATE_NONEXISTENT | 19 | 不存在的程式 |
同一個程式,在不同狀態下,其 oom_adj 是不一樣的。
kill 並移除程式
在 ActivityManagerService 呼叫 updateOomAdjLocked() 時,會判斷程式是否需要被殺死,若是,則呼叫 ProceeRecord::kill() 方法殺死該程式:
注: 目前 LMK_PROCREMOVE 命令暫時無用,即未執行有意義的程式碼。
結語
Android 記憶體系列至此,全部完結?。如有錯誤,還望不吝賜教?。