Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0)

本篇是Android后台杀死系列的第三篇,前面两篇已经对后台杀死注意事项杀死恢复机制作了分析,本篇主要讲解的是Android后台杀死原理。相对于后台杀死恢复,LowMemoryKiller原理相对简单,而且在网上仍是能找到很多资料的,不过,因为Android不一样版本在框架层的实现有一些不一样,网上的分析也可能是针对一个Android版本,本文简单作了如下区分对比。LowMemoryKiller(低内存杀手)是Andorid基于oomKiller原理所扩展的一个多层次oomKiller,OOMkiller(Out Of Memory Killer)是在Linux系统没法分配新内存的时候,选择性杀掉进程,到oom的时候,系统可能已经不太稳定,而LowMemoryKiller是一种根据内存阈值级别触发的内存回收的机制,在系统可用内存较低时,就会选择性杀死进程的策略,相对OOMKiller,更加灵活。在详细分析其原理与运行机制以前,不妨本身想一下,假设让你设计一个LowMemoryKiller,你会如何作,这样一个系统须要什么功能模块呢?html

  • 进程优先级定义:只有有了优先级,才能决定先杀谁,后杀谁android

  • 进程优先级的动态管理:一个进程的优先级不该该是固定不变的,须要根据其变更而动态变化,好比前台进程切换到后台优先级确定要下降git

  • 进程杀死的时机,何时须要挑一个,或者挑多个进程杀死缓存

  • 如何杀死网络

以上几个问题即是一个MemoryKiller模块须要的基本功能,Android底层采用的是Linux内核,其进程管理都是基于Linux内核,LowMemoryKiller也相应的放在内核模块,这也意味着用户空间对于后台杀死不可见,就像AMS彻底不知道一个APP是否被后台杀死,只有在AMS唤醒APP的时候,才知道APP是否被LowMemoryKiller杀死过。其实LowmemoryKiller的原理是很清晰的,先看一下总体流程图,再逐步分析:架构

App操做影响进程优先级

先记住两点 :app

  1. LowMemoryKiller是被动杀死进程框架

  2. Android应用经过AMS,利用proc文件系统更新进程信息socket

Android应用进程优先级及oomAdj

Android会尽量长时间地保持应用存活,但为了新建或运行更重要的进程,可能须要移除旧进程来回收内存,在选择要Kill的进程的时候,系统会根据进程的运行状态做出评估,权衡进程的“重要性“,其权衡的依据主要是四大组件。若是须要缩减内存,系统会首先消除重要性最低的进程,而后是重要性略逊的进程,依此类推,以回收系统资源。在Android中,应用进程划分5级(摘自Google文档):Android中APP的重要性层次一共5级:ide

  • 前台进程(Foreground process)

  • 可见进程(Visible process)

  • 服务进程(Service process)

  • 后台进程(Background process)

  • 空进程(Empty process)

前台进程

用户当前操做所必需的进程。若是一个进程知足如下任一条件,即视为前台进程:

  • 包含正在交互的Activity(resumed

  • 包含绑定到正在交互的Activity的Service

  • 包含正在“前台”运行的Service(服务已调用startForeground())

  • 包含正执行一个生命周期回调的Service(onCreate()、onStart() 或 onDestroy())

  • 包含一个正执行其onReceive()方法的BroadcastReceiver

一般,在任意给定时间前台进程都为数很少。只有在内存不足以支持它们同时继续运行这一万不得已的状况下,系统才会终止它们。 此时,设备每每已达到内存分页状态,所以须要终止一些前台进程来确保用户界面正常响应。

可见进程

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 若是一个进程知足如下任一条件,即视为可见进程:

  • 包含不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,若是前台 Activity 启动了一个对话框,容许在其后显示上一Activity,则有可能会发生这种状况。

  • 包含绑定到可见(或前台)Activity 的 Service。

可见进程被视为是极其重要的进程,除非为了维持全部前台进程同时运行而必须终止,不然系统不会终止这些进程。

服务进程

正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,可是它们一般在执行一些用户关心的操做(例如,在后台播放音乐或从网络下载数据)。所以,除非内存不足以维持全部前台进程和可见进程同时运行,不然系统会让服务进程保持运行状态。

后台进程

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 一般会有不少后台进程在运行,所以它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。若是某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,由于当用户导航回该 Activity 时,Activity会恢复其全部可见状态。 有关保存和恢复状态、或者异常杀死恢复能够参考前两篇 文章。

空进程

不含任何活动应用组件的进程。保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间,这就是所谓热启动 。为了使系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程。

根据进程中当前活动组件的重要程度,Android会将进程评定为它可能达到的最高级别。例如,若是某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。此外,一个进程的级别可能会因其余进程对它的依赖而有所提升,即服务于另外一进程的进程其级别永远不会低于其所服务的进程。 例如,若是进程 A 中的内容提供程序为进程 B 中的客户端提供服务,或者若是进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程B一样重要。

经过Google文档,对不一样进程的重要程度有了一个直观的认识,下面看一下量化到内存是什么样的呈现形式,这里针对不一样的重要程度,作了进一步的细分,定义了重要级别ADJ,并将优先级存储到内核空间的进程结构体中去,供LowmemoryKiller参考:

ADJ优先级 优先级 对应场景
UNKNOWN_ADJ 16 通常指将要会缓存进程,没法获取肯定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值(不可见进程可能在任什么时候候被杀死)
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值(不可见进程可能在任什么时候候被杀死)
SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一个App的进程(好比APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ)
HOME_APP_ADJ 6 Home进程
SERVICE_ADJ 5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程(这个不太了解)
PERCEPTIBLE_APP_ADJ 2 可感知进程,好比后台音乐播放
VISIBLE_APP_ADJ 1 可见进程(可见,可是没能获取焦点,好比新进程仅有一个悬浮Activity,Visible process)
FOREGROUND_APP_ADJ 0 前台进程(正在展现是APP,存在交互界面,Foreground process)
PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ -12 系统persistent进程,好比telephony
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 native进程(不被系统管理)

以上介绍的目的只有一点:Android的应用进程是有优先级的,它的优先级跟当前是否存在展现界面,以及是否能被用户感知有关,越是被用户感知的的应用优先级越高(系统进程不考虑)。

Android应用的优先级是如何更新的

APP中不少操做均可能会影响进程列表的优先级,好比退到后台、移到前台等,都会潜在的影响进程的优先级,咱们知道Lowmemorykiller是经过遍历内核的进程结构体队列,选择优先级低的杀死,那么APP操做是如何写入到内核空间的呢?Linxu有用户间跟内核空间的区分,不管是APP仍是系统服务,都是运行在用户空间,严格说用户控件的操做是没法直接影响内核空间的,更不用说更改进程的优先级。其实这里是经过了Linux中的一个proc文件体统,proc文件系统能够简单的看可能是内核空间映射成用户能够操做的文件系统,固然不是全部进程都有权利操做,经过proc文件系统,用户空间的进程就可以修改内核空间的数据,好比修改进程的优先级,在Android家族,5.0以前的系统是AMS进程直接修改的,5.0以后,是修改优先级的操做被封装成了一个独立的服务-lmkd,lmkd服务位于用户空间,其做用层次同AMS、WMS相似,就是一个普通的系统服务。咱们先看一下5.0以前的代码,这里仍然用4.3的源码看一下,模拟一个场景,APP只有一个Activity,咱们主动finish掉这个Activity,APP就回到了后台,这里要记住,虽然没有可用的Activity,可是APP自己是没哟死掉的,这就是所谓的热启动,先看下大致的流程:

App操做影响进程优先级

如今直接去AMS看源码:

ActivityManagerService

public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) {
     ...
    synchronized(this) {
       
        final long origId = Binder.clearCallingIdentity();
        boolean res = mMainStack.requestFinishActivityLocked(token, resultCode,
                resultData, "app-request", true);
     ...
    }
}

一开始的流程跟startActivity相似,首先是先暂停当前resume的Activity,其实也就是本身,

final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode,
            Intent resultData, String reason, boolean immediate, boolean oomAdj) {
         ...
            if (mPausingActivity == null) {
                if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r);
                if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false");
                startPausingLocked(false, false);
            }
            ...
    }

pause掉当前Activity以后,还须要唤醒上一个Activity,若是当前APP的Activity栈里应经空了,就回退到上一个应用或者桌面程序,唤醒流程就不在讲解了,由于在AMS恢复异常杀死APP的那篇已经说过,这里要说的是唤醒以后对这个即将退回后台的APP的操做,这里注意与startActivity不一样的地方,看下面代码:

ActivityStack

private final void completePauseLocked() {
    ActivityRecord prev = mPausingActivity;
     
    if (prev != null) {
        if (prev.finishing) {
        一、 不一样点
       <!--主动finish的时候,走的是这个分支,状态变换的细节请本身查询代码-->
            prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false);
        } 
        ...
        二、相同点         
     if (!mService.isSleeping()) {
        resumeTopActivityLocked(prev);
    }

看一下上面的两个关键点1跟2,1是同startActivity的completePauseLocked不一样的地方,主动finish的prev.finishing是为true的,所以会执行finishCurrentActivityLocked分支,将当前pause的Activity加到mStoppingActivities队列中去,而且唤醒下一个须要走到到前台的Activity,唤醒后,会继续执行stop:

private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r,
            int index, int mode, boolean oomAdj) {
        if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
            if (!mStoppingActivities.contains(r)) {
                mStoppingActivities.add(r);
                 ...
            }
               ....
            return r;
        }
        ...
    }

让咱们再回到resumeTopActivityLocked继续看,resume以后会回调completeResumeLocked函数,继续执行stop,这个函数经过向Handler发送IDLE_TIMEOUT_MSG消息来回调activityIdleInternal函数,最终执行destroyActivityLocked销毁ActivityRecord,

final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
        ...
   if (next.app != null && next.app.thread != null) {                    ...
            try {
                。。。
                next.app.thread.scheduleResumeActivity(next.appToken,
                        mService.isNextTransitionForward());
                  ..。
            try {
                next.visible = true;
                completeResumeLocked(next);
            }  
            ....
         }

在销毁Activity的时候,若是当前APP的Activity堆栈为空了,就说明当前Activity没有可见界面了,这个时候就须要动态更新这个APP的优先级,详细代码以下:

final boolean destroyActivityLocked(ActivityRecord r,
            boolean removeFromApp, boolean oomAdj, String reason) {
            ...
       if (hadApp) {
            if (removeFromApp) {
                // 这里动ProcessRecord里面删除,可是没从history删除
                int idx = r.app.activities.indexOf(r);
                if (idx >= 0) {
                    r.app.activities.remove(idx);
                }
                ...
                if (r.app.activities.size() == 0) {
                    // No longer have activities, so update oom adj.
                    mService.updateOomAdjLocked();
                 ...
       }

最终会调用AMS的updateOomAdjLocked函数去更新进程优先级,在4.3的源码里面,主要是经过Process类的setOomAdj函数来设置优先级:

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj,
        int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) {
    ...
    计算优先级
    computeOomAdjLocked(app, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP, false, doingAll);
     。。。
      <!--若是不相同,设置新的OomAdj-->
       
    if (app.curAdj != app.setAdj) {
        if (Process.setOomAdj(app.pid, app.curAdj)) {
        ...
}

Process中setOomAdj是一个native方法,原型在android_util_Process.cpp中

android_util_Process.cpp

jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz,
                                      jint pid, jint adj)
{
#ifdef HAVE_OOM_ADJ
    char text[64];
    sprintf(text, "/proc/%d/oom_adj", pid);
    int fd = open(text, O_WRONLY);
    if (fd >= 0) {
        sprintf(text, "%d", adj);
        write(fd, text, strlen(text));
        close(fd);
    }
    return true;
#endif
    return false;
}

能够看到,在native代码里,就是经过proc文件系统修改内核信息,这里就是动态更新进程的优先级oomAdj,以上是针对Android4.3系统的分析,以后会看一下5.0以后的系统是如何实现的。下面是4.3更新oomAdj的流程图,注意红色的执行点:

LowMemoryKiller更新进程oomAdj

Android5.0以后框架层的实现:LMKD服务

Android5.0将设置进程优先级的入口封装成了一个独立的服务lmkd服务,AMS再也不直接访问proc文件系统,而是经过lmkd服务来进行设置,从init.rc文件中看到服务的配置。

service lmkd /system/bin/lmkd
    class core
    critical
    socket lmkd seqpacket 0660 system system

从配置中能够看出,该服务是经过socket与其余进行进程进行通讯,其实就是AMS经过socket向lmkd服务发送请求,让lmkd去更新进程的优先级,lmkd收到请求后,会经过/proc文件系统去更新内核中的进程优先级。首先看一下5.0中这一块AMS有什么改变,其实大部分流程跟以前4.3源码相似,咱们只看一下不一样地方

ActivityManagerService

private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
        ProcessRecord TOP_APP, boolean doingAll, long now) {
    ...
    computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
    ...
    applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
}

private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
        long nowElapsed) {
    boolean success = true;

    if (app.curRawAdj != app.setRawAdj) {
        app.setRawAdj = app.curRawAdj;
    }

    int changes = 0;
      不一样点1
    if (app.curAdj != app.setAdj) {
        ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj);
        if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG_OOM_ADJ,
                "Set " + app.pid + " " + app.processName + " adj " + app.curAdj + ": "
                + app.adjType);
        app.setAdj = app.curAdj;
        app.verifiedAdj = ProcessList.INVALID_ADJ;
    }

从上面的不一样点1能够看出,5.0以后是经过ProcessList类去设置oomAdj,其实这里就是经过socket与LMKD服务进行通讯,向lmkd服务传递给LMK_PROCPRIO命令去更新进程优先级:

public static final void setOomAdj(int pid, int uid, int amt) {
    if (amt == UNKNOWN_ADJ)
        return;
   long start = SystemClock.elapsedRealtime();
    ByteBuffer buf = ByteBuffer.allocate(4 * 4);
    buf.putInt(LMK_PROCPRIO);
    buf.putInt(pid);
    buf.putInt(uid);
    buf.putInt(amt);
    writeLmkd(buf);
    long now = SystemClock.elapsedRealtime();
      }    
    
private static void writeLmkd(ByteBuffer buf) {
         for (int i = 0; i < 3; i++) {
        if (sLmkdSocket == null) {
          if (openLmkdSocket() == false) {
            ...
        try {
            sLmkdOutputStream.write(buf.array(), 0, buf.position());
            return;
            ...
    }

其实就是openLmkdSocket打开本地socket端口,并将优先级信息发送过去,那么lmkd服务端如何处理的呢,init.rc里配置的服务是在开机时启动的,来看看lmkd服务的入口:main函数

lmkd.c函数

int main(int argc __unused, char **argv __unused) {
    struct sched_param param = {
            .sched_priority = 1,
    };

    mlockall(MCL_FUTURE);
    sched_setscheduler(0, SCHED_FIFO, &param);
    if (!init())
        mainloop();

    ALOGI("exiting");
    return 0;
}

很简单,打开一个端口,并经过mainloop监听socket,若是有请求到来,就解析命令并执行,刚才传入的LMK_PROCPRIO命令对应的操做就是cmd_procprio,用来更新oomAdj,其更新新机制仍是经过proc文件系统,不信?看下面代码:

static void cmd_procprio(int pid, int uid, int oomadj) {
    struct proc *procp;
    。。。
    仍是利用/proc文件系统进行更新
    snprintf(path, sizeof(path), "/proc/%d/oom_score_adj", pid);
    snprintf(val, sizeof(val), "%d", lowmem_oom_adj_to_oom_score_adj(oomadj));
    writefilestring(path, val);
   。。。
}

简单的流程图以下,同4.3不一样的地方

Android5.0以后的LMKD服务

以上就分析完了用户空间的操做如何影响到进程的优先级,而且将新的优先级写到内核中。最后看一下LomemoryKiller在何时、如何根据优先级杀死进程的:

LomemoryKiller内核部分:如何杀死

LomemoryKiller属于一个内核驱动模块,主要功能是:在系统内存不足的时候扫描进程队列,找到低优先级(也许说性价比低更合适)的进程并杀死,以达到释放内存的目的。对于驱动程序,入口是__init函数,先看一下这个驱动模块的入口:

static int __init lowmem_init(void)
{
    register_shrinker(&lowmem_shrinker);
    return 0;
}

能够看到在init的时候,LomemoryKiller将本身的lowmem_shrinker入口注册到系统的内存检测模块去,做用就是在内存不足的时候能够被回调,register_shrinker函数是一属于另外一个内存管理模块的函数,若是必定要根下去的话,能够看一下它的定义,其实就是加到一个回调函数队列中去:

void register_shrinker(struct shrinker *shrinker)
{
    shrinker->nr = 0;
    down_write(&shrinker_rwsem);
    list_add_tail(&shrinker->list, &shrinker_list);
    up_write(&shrinker_rwsem);
}

最后,看一下,当内存不足触发回调的时候,LomemoryKiller是如何找到低优先级进程,并杀死的:入口函数就是init时候注册的lowmem_shrink函数(4.3源码,后面的都有微调但原理大概相似):

static int lowmem_shrink(int nr_to_scan, gfp_t gfp_mask)
{
    struct task_struct *p;
    。。。
    关键点1 找到当前的内存对应的阈值
    for(i = 0; i < array_size; i++) {
        if (other_free < lowmem_minfree[i] &&
            other_file < lowmem_minfree[i]) {
            min_adj = lowmem_adj[i];
            break;
        }
    }
    。。。
    关键点2 找到优先级低于这个阈值的进程,并杀死
    
    read_lock(&tasklist_lock);
    for_each_process(p) {
        if (p->oomkilladj < min_adj || !p->mm)
            continue;
        tasksize = get_mm_rss(p->mm);
        if (tasksize <= 0)
            continue;
        if (selected) {
            if (p->oomkilladj < selected->oomkilladj)
                continue;
            if (p->oomkilladj == selected->oomkilladj &&
                tasksize <= selected_tasksize)
                continue;
        }
        selected = p;
        selected_tasksize = tasksize;

    }
    if(selected != NULL) {
        force_sig(SIGKILL, selected);
        rem -= selected_tasksize;
    }
    lowmem_print(4, "lowmem_shrink %d, %x, return %d\n", nr_to_scan, gfp_mask, rem);
    read_unlock(&tasklist_lock);
    return rem;
}

先看关键点1:其实就是肯定当前低内存对应的阈值;关键点2 :找到比该阈值优先级低,切task多的进程,将其杀死。如何杀死的呢?很直接,经过Linux的中的信号量,发送SIGKILL信号直接将进程杀死。到这就分析完了LomemoryKiller内核部分如何工做的。其实很简单,一句话:被动扫描,找到低优先级的进程,杀死。

总结

经过本篇文章,但愿你们能有如下几点认知:

  • Android APP进程是有优先级的的,与进程是否被用户感知有直接关系

  • APP切换等活动均可能形成进程优先级的变化,都是利用AMS,并经过proc文件设置到内核的

  • LowmemoryKiller运行在内核,在内存须要缩减的时候,会选择低优先级的进程杀死

至于更加细节的内存的缩减、优先级的计算也许未来会放到单独的文章中说明,本文的目的是:能让你们对LowmemoryKiller的概念以及运行机制有个简单了解。

参考文档

Android应用程序启动过程源代码分析
Android Framework架构浅析之【近期任务】
Android Low Memory Killer介绍
Android开发之InstanceState详解
对Android近期任务列表(Recent Applications)的简单分析
Android 操做系统的内存回收机制
Android LowMemoryKiller原理分析 精
Android进程生命周期与ADJ
Linux下/proc目录简介
Android系统中的进程管理:进程的建立 精
Google文档--进程和线程

相关文章
相关标签/搜索