讀書筆記,如需轉載,請註明做者:Yuloran (t.cn/EGU6c76)java
筆者在以前的文章《分析並優化 Android 應用內存佔用》中提到,爲了不 Cached Pages 太少時致使設備卡頓、死機、重啓等狀況,Android 引入了 LowMemoryKiller(源自 Linux OOM Killer) 機制,提早回收優先級比較低的進程所佔的資源,以保證一個較好的用戶體驗。進程優先級列表由 SystemServer 進程維護:linux
其中 Cached 進程列表,使用的是 LRU 算法。android
每一個 Java 進程都有一個相關聯的 ProcessRecord 對象,其成員變量 curAdj 就表示該進程當前狀態下的優先級:git
int curAdj; // Current OOM adjustment for this process
複製代碼
當設備可用內存低於 LMK 閾值時,LMK 便會根據進程的優先級,逐級殺死進程並釋放其佔用的資源。算法
建議先閱讀筆者前四篇博文,以對 Android 內存相關有一個比較全面而深刻的瞭解:shell
本篇博文爲 Android 內存系列的最後一篇。api
本小節摘自 Google Android Developers 《進程和線程》,LMK 殺死進程時,將聽從 空進程 -> 後臺進程 -> 服務進程 -> 可見進程 -> 前臺進程
的順序:數組
用戶當前操做所必需的進程。若是一個進程知足如下任一條件,即視爲前臺進程:緩存
一般,在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。bash
沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 若是一個進程知足如下任一條件,即視爲可見進程:
可見進程被視爲是極其重要的進程,除非爲了維持全部前臺進程同時運行而必須終止,不然系統不會終止這些進程。
正在運行已使用 startService() 方法啓動的服務且不屬於上述兩個更高類別進程的進程。
儘管服務進程與用戶所見內容沒有直接關聯,可是它們一般在執行一些用戶關心的操做(例如,在後臺播放音樂或從網絡下載數據)。所以,除非內存不足以維持全部前臺進程和可見進程同時運行,不然系統會讓服務進程保持運行狀態。
包含目前對用戶不可見的 Activity 的進程(已調用 Activity 的 onStop() 方法)。
這些進程對用戶體驗沒有直接影響,系統可能隨時終止它們,以回收內存供前臺進程、可見進程或服務進程使用。 一般會有不少後臺進程在運行,所以它們會保存在 LRU (最近最少使用)列表中,以確保包含用戶最近查看的 Activity 的進程最後一個被終止。
不含任何活動應用組件的進程。
保留這種進程的的惟一目的是用做緩存,以縮短下次在其中運行組件所需的啓動時間。 爲使整體系統資源在進程緩存和底層內核緩存之間保持平衡,系統每每會終止這些進程。
Andorid 的 Low Memory Killer 是在標準的 Linux Kernel 的 OOM Killer 基礎上修改而來的一種內存管理機制。當系統內存不足時,殺死不重要的進程以釋放其內存。LMK 的關鍵參數有 3 個:
取值範圍定義:
-> 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 值,具體分析進程是在什麼狀態下被殺死的。
取值範圍定義:
-> 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() 的過程當中會調用該方法,根據設備的分辨率初始化或更新閾值的大小。
取值範圍定義在 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 是由 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() |
在 ActivityManagerService 調用 updateConfiguration() 的過程當中會調用 ProcessList::updateOomLevels() 方法,根據設備的分辨率調整閾值的大小,經過 LMK_TARGET 命令,通知 lmkd(low memory killer deamon)分別向 /sys/module/lowmemorykiller/parameters 目錄下的 minfree 和 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 是不同的。
在 ActivityManagerService 調用 updateOomAdjLocked() 時,會判斷進程是否須要被殺死,如果,則調用 ProceeRecord::kill() 方法殺死該進程:
注: 目前 LMK_PROCREMOVE 命令暫時無用,即未執行有意義的代碼。
Android 內存系列至此,所有完結😁。若有錯誤,還望不吝賜教🤝。