阅读 2157

Android LowMemoryKiller 简介

读书笔记,如需转载,请注明作者: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 中:

-> 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 内存系列至此,全部完结😁。如有错误,还望不吝赐教🤝。

参考资料

关注下面的标签,发现更多相似文章
评论