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

参考资料

相关文章
相关标签/搜索