Android LowMemoryKiller 簡介

讀書筆記,如需轉載,請註明做者: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 殺死進程時,將聽從 空進程 -> 後臺進程 -> 服務進程 -> 可見進程 -> 前臺進程 的順序:數組

前臺進程

用戶當前操做所必需的進程。若是一個進程知足如下任一條件,即視爲前臺進程:緩存

  • 託管用戶正在交互的 Activity(已調用 Activity 的 onResume() 方法)
  • 託管某個 Service,該 Service 綁定到用戶正在交互的 Activity
  • 託管正在「前臺」運行的 Service(服務已調用 startForeground())
  • 託管正執行一個生命週期回調的 Service(onCreate()、onStart() 或 onDestroy())
  • 託管正執行其 onReceive() 方法的 BroadcastReceiver

一般,在任意給定時間前臺進程都爲數很少。只有在內存不足以支持它們同時繼續運行這一萬不得已的狀況下,系統纔會終止它們。bash

可見進程

沒有任何前臺組件、但仍會影響用戶在屏幕上所見內容的進程。 若是一個進程知足如下任一條件,即視爲可見進程:

  • 託管不在前臺、但仍對用戶可見的 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 中:

-> lowmemorykiller.c

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 內存系列至此,所有完結😁。若有錯誤,還望不吝賜教🤝。

參考資料

相關文章
相關標籤/搜索