Android系统进程优先级策略 ADJ

本篇文章是基于Android9.0来介绍Android系统关于进程的优先级是如何定义和管理的。

概述

1.进程

进程-Process 是程序的一个运行实例。通常会有唯一一个pid与之对应。但pid不是绝对唯一的,当进程死亡后pid会被回收给另外的进程使用。在Android世界里,App开发者很容易认为系统的四大组件就是进程的载体,实际上,它们不能算是完整的进程实例,最多只能算是进程的组成部分。由于Android系统框架中,系统对进程的创建和管理进行了封装,每当我们在启动四大组件Activity, BroadcastReceiver,ContentProvider,Service任意一个的时候,系统就会去检查该组件所在的进程是否已经存在,如果不存在,框架就会自动调用startProcessLocked函数去创建进程。当然一个app可以存在多个进程,多个app也可以运行在同一个进程下,我们可以通过Android:process来设置。

2.优先级

Android系统框架设计理念里是希望对用户很重要的进程尽可能的长期存活,以此来提高用户体验。我们知道Android app在启动的时候会去做很多事情,比如系统会检测该app进程是否存在,如果不存在则需要通知zygote进程去fork app进程,同时完成application信息的初始化工作。此时如果app的进程存在,那么就会省略这个步骤,直接快速唤起app。对于app来说也是一样,通过进程保活可以去实现更多的功能,但是如果所有的app不管有用没用的都存活下来了,系统的资源毕竟是有限的,系统内存很快就会枯竭而亡,这时就需要有合理地进程回收机制了。那么究竟该怎么回收进程呢?其实系统是根据各个app四大组件的状态来决定进程的优先级值adj。系统会根据一定的策略先去回收优先级最低的(同时也是adj值最大的),其次再回收优先级略低的,依次类推,直到回收了足够的系统资源,保证系统正常运转。

3.adj取值范围及含义

ADJ级别取值含义
NATIVE_ADJ-1000native进程
SYSTEM_ADJ-900仅指system_server进程
PERSISTENT_PROC_ADJ-800系统persistent进程
PERSISTENT_SERVICE_ADJ-700关联着系统或persistent进程
FOREGROUND_APP_ADJ0前台进程
VISIBLE_APP_ADJ100可见进程
PERCEPTIBLE_APP_ADJ200可感知进程,比如后台音乐播放
BACKUP_APP_ADJ300备份进程
HEAVY_WEIGHT_APP_ADJ400重量级进程
SERVICE_ADJ500服务进程
HOME_APP_ADJ600Home进程
PREVIOUS_APP_ADJ700上一个进程
SERVICE_B_ADJ800B List中的Service
CACHED_APP_MIN_ADJ900不可见进程的adj最小值
CACHED_APP_MAX_ADJ906不可见进程的adj最大值

从Android 7.0开始,ADJ采用100、200、300;在这之前的版本ADJ采用数字1、2、3,这样的调整可以更进一步地细化进程的优先级,比如在VISIBLE_APP_ADJ(100)与PERCEPTIBLE_APP_ADJ(200)之间,可以有ADJ=101、102级别的进程。

关于进程的优先级可以通过如下的命令查看

adb shell
cd /proc/pid/
oom_adj   oom_score  oom_score_adj
cat oom_adj
0 表示前台进程FOREGROUND_APP_ADJ

省去lmk对oom_score_adj的计算过程,Android 7.0之前的版本,oom_score_adj= oom_adj * 1000/17; 而Android 7.0开始,oom_score_adj= oom_adj,不用再经过一次转换。

4.LowMemoryKiller

Android的Low Memory Killer基于Linux的OOM(Out Of Memory Killer)机制,在Linux中,内存是以页面为单位分配的,OOM的策略更多的是用于分配内存不足时触发,将得分最高的进程杀掉。而lmk则会每隔一段时间检查一次,当系统剩余可用内存较低时,便会触发杀进程的策略,根据不同的剩余内存档位来选择杀不同优先级的进程,而不是等到OOM时再来杀进程,真正OOM时系统可能已经处于异常状态,系统更希望的是未雨绸缪,在内存很低时来杀掉一些优先级较低的进程来保障后续操作的顺利进行。这里不详细说,接下来会有一篇关于Low Memory Killer的文章来详细介绍。
那么在哪里可以看到各个剩余内存档位呢?可以通过如下方式:

frameworks/base/services/core/java/com/android/server/am/ProcessList.java
// 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
};
从ProcessList.java中关于mOomAdj的定义及注释中我们可以发现,
系统一共定义了6个档位,
分别对应了上表格中列出的几种进程类型。
adb shell
cat /sys/module/lowmemorykiller/parameters/adj
0,100,200,300,900,906

adj值对应如下:
FOREGROUND_APP_ADJ(0)
VISIBLE_APP_ADJ(100)
PERCEPTIBLE_APP_ADJ(200)
BACKUP_APP_ADJ(300)
CACHED_APP_MIN_ADJ(900)
CACHED_APP_MAX_ADJ(906)

cat /sys/module/lowmemorykiller/parameters/minfree
18432,23040,27648,32256,85296,120640
由于一个page=4k,对应的换算公式是:value*4/1024,换算结果如下:
72M,90M,108M,126M,144M,180M

剩余内存值档位对应如下:
FOREGROUND_APP_ADJ(72M)
VISIBLE_APP_ADJ(90M)
PERCEPTIBLE_APP_ADJ(108M)
BACKUP_APP_ADJ(126M)
CACHED_APP_MIN_ADJ(144M)
CACHED_APP_MAX_ADJ(180M)

进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。举例:当系统剩余空闲内存低于某阈值(比如140MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。
在updateOomLevels()过程,会根据手机屏幕尺寸或内存大小来调整scale,默认大多数手机内存都大于700MB,则scale等于1。对于64位手机,cached进程的阈值会更大些。

private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
        // Scale buckets from avail memory: at 300MB we use the lowest values to
        // 700MB or more for the top values.
        float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
        ...
        // Scale buckets from screen size.
        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;
        if (scale < 0) scale = 0;
        else if (scale > 1) scale = 1;
        ...
        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));
        }
}

ADJ详解

1.ADJ优先级小于0的,就是说正常情况下肯定不会被杀死,即使被杀死了也会被重启的进程。

  • NATIVE_ADJ(-1000):是由init进程fork出来的Native进程,并不受system管控
  • SYSTEM_ADJ(-900):是指system_server进程
  • PERSISTENT_PROC_ADJ(-800): 是指在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,persistent进程一般情况并不会被杀,即便被杀或者发生Crash系统会立即重新拉起该进程
  • PERSISTENT_SERVICE_ADJ(-700):是由startIsolatedProcess()方式启动的进程,或者是由system_server或者persistent进程所绑定(并且带有BIND_ABOVE_CLIENT或者BIND_IMPORTANT)的服务进程

2.ADJ优先级大于等于0的

  • FOREGROUND_APP_ADJ(0): 正在前台运行的进程,是不应该被干掉的进程
    1.正处于resumed状态的Activity; app.adjType = "top-activity"
    2.正执行一个生命周期回调的Service(比如执行onCreate,onStartCommand,onDestroy等); app.adjType = "exec-service";
    3.正执行onReceive()的BroadcastReceiver; app.adjType = "broadcast";
    4.通过startInstrumentation()启动的进程; app.adjType = "instrumentation"
    5.当客户端进程activity里面调用bindService()方法时flags带有BIND_ADJUST_WITH_ACTIVITY参数,并且该activity处于可见状态,则当前服务进程也属于前台进程; app.adjType = "service";
    6.对于provider进程,还有以下两个条件能成为前台进程:

    • 当Provider的客户端进程ADJ<=FOREGROUND_APP_ADJ时,则Provider进程ADJ等于FOREGROUND_APP_ADJ; adjType = "provider";
    • 当Provider有外部进程依赖,也就是调用了getContentProviderExternal()方法,则ADJ至少等于FOREGROUND_APP_ADJ; app.adjType = "ext-provider";
  • VISIBLE_APP_ADJ(100): 可见进程,比如生命周期还没有执行到onstop的activity,有widget组件在桌面的等还是可以被用户看到的; app.adjType = "vis-activity";
    从Android P开始,进一步细化ADJ级别,增加了VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1=99,是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。
    关于TaskRecord的mLayerRank的计算方式是在updateOomAdjLocked()过程调用ASS的rankTaskLayersIfNeeded()方法

  • PERCEPTIBLE_APP_ADJ(200): 当该进程存在不可见的Activity,但Activity正处于PAUSING、PAUSED、STOPPING状态,则为PERCEPTIBLE_APP_ADJ;app.adjType = "pause-activity" or app.adjType = "stop-activity";
    满足以下任一条件的进程也属于可感知进程:
    1.foregroundServices非空:前台服务进程,执行startForegroundService()方法; app.adjType = "fg-service";
    2.app.forcingToImportant非空:执行setProcessImportant()方法,比如Toast弹出过程; app.adjType = "force-imp";
    3.hasOverlayUi非空:非activity的UI位于屏幕最顶层,比如显示类型TYPE_APPLICATION_OVERLAY的窗口; app.adjType = "has-overlay-ui";

  • BACKUP_APP_ADJ(300):执行bindBackupAgent()过程的进程;
    1.执行bindBackupAgent()过程,设置mBackupTarget值;
    2.执行clearPendingBackup()或unbindBackupAgent()过程,置空mBackupTarget值;

  • HEAVY_WEIGHT_APP_ADJ(400): realStartActivityLocked()过程,当应用的privateFlags标识PRIVATE_FLAG_CANT_SAVE_STATE的进程;
    HEAVY_WEIGHT_APP一般是在后台运行,要避免它被干掉。Value set in system/rootdir/init.rc on startup.

  • SERVICE_ADJ(500):没有启动过Activity,并且30分钟之内活跃过的服务进程。startRequested为true,则代表执行startService()且没有stop的进程; app.adjType = "started-services";

  • HOME_APP_ADJ(600):当类型为ACTIVITY_TYPE_HOME的应用,比如桌面APP

  • PREVIOUS_APP_ADJ(700):
    1.用户上一个使用的包含UI的进程,为了给用户在两个APP之间更好的切换体验,将上一个进程ADJ设置到PREVIOUS_APP_ADJ的档次。 当activityStoppedLocked()过程会更新上一个应用。
    2.当provider进程,上一次使用时间不超过20S的情况下,优先级不低于PREVIOUS_APP_ADJ。provider进程这个是Android 7.0以后新增的逻辑 ,这样做的好处是在内存比较低的情况下避免拥有provider的进程出现颠簸,也就是启动后杀,然后又被拉。

  • SERVICE_B_ADJ(800):进程由SERVICE_ADJ(500)降低到SERVICE_B_ADJ(800),有以下两种情况:
    1.A类Service占比过高:当A类Service个数 > Service总数的1/3时,则加入到B类Service。换句话说,B Service的个数至少是A Service的2倍。
    2.内存紧张&&A类Service占用内存较高:当系统内存紧张级别(mLastMemoryLevel)高于ADJ_MEM_FACTOR_NORMAL,且该应用所占内存lastPss大于或等于CACHED_APP_MAX_ADJ级别所对应的内存阈值的1/3(默认值阈值约等于110MB)
    内存因子ADJ_MEM_FACTOR共有4个级别,当前处于哪个内存因子级别,取决于当前进程中cached进程和空进程的个数

final int numCachedAndEmpty = numCached + numEmpty;
int memFactor;
if (numCached <= mConstants.CUR_TRIM_CACHED_PROCESSES
        && numEmpty <= mConstants.CUR_TRIM_EMPTY_PROCESSES) {
    if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
    } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;
    } else {
        memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;
    }
} else {
    memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;
}
内存因子取值先决条件
ADJ_MEM_FACTOR_CRITICAL3Cached+Empty<=3
ADJ_MEM_FACTOR_LOW2Cached+Empty<=5
ADJ_MEM_FACTOR_MODERATE1Cached<=5 && Empty<=8
ADJ_MEM_FACTOR_NORMAL0Cached>5或者Empty>8
  • 最大缓存进程个数:CUR_MAX_CACHED_PROCESSES = MAX_CACHED_PROCESSES = 32
  • 最大空进程个数: CUR_MAX_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/2 = 16
  • Trim空进程上限:CUR_TRIM_EMPTY_PROCESSES = MAX_CACHED_PROCESSES/4 = 8
  • Trim缓存进程上限:CUR_TRIM_CACHED_PROCESSES = MAX_CACHED_PROCESSES/6 = 5

注:用于限制empty或cached进程的上限为16个,
并且empty超过8个时会清理掉30分钟没有活跃的进程。 cached和empty主要是区别是否有Activity
系统会有相应的log输出:

//默认cachedProcessLimit=16
if (numCached > cachedProcessLimit) {
    app.kill("cached #" + numCached, true);
}
//默认CUR_TRIM_EMPTY_PROCESSES=8, 且满足30min
if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
        && app.lastActivityTime < oldTime) {
      app.kill("empty for "+ (
      (oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
         / 1000) + "s", true);
}
//默认cachedProcessLimit=16
if (numEmpty > emptyProcessLimit) {
     app.kill("empty #" + numEmpty, true);
}
  • CACHED_APP_MIN_ADJ(900):
    缓存进程优先级从CACHED_APP_MIN_ADJ(900)到 CACHED_APP_MAX_ADJ(906)。
    ADJ的转换算法:
    cached: 900, 901, 903, 905
    empty: 900, 902, 904, 906

     

    1.foregroundActivities代表当前不是前台(FOREGROUND_APP_ADJ)进程,并且存在Activity的进程,当该Activity窗口不可见,并且不处于PAUSING(正在)、PAUSED(onPause个)、STOPPING的任一状态的情况下,则设置该进程为PROCESS_STATE_CACHED_ACTIVITY。
    app.adjType = "cch-act";
    2.当该进程Service的客户端进程存在Activity或者是treatLikeActivity的进程,其进程状态都是cached进程。
    app.adjType = "cch-as-act";
    app.adjType = "cch-client-act";

总结

Android进程优先级ADJ的每一个ADJ级别往往都有多种场景,使用adjType完美地区分相同ADJ下的不同场景; 不同ADJ进程所对应的schedGroup不同,从而分配的CPU资源也不同,schedGroup大体分为TOP(T)、前台(F)、后台(B); ADJ跟AMS中的procState有着紧密的联系。

adj:通过调整oom_score_adj来影响进程寿命(Lowmemorykiller杀进程策略);
schedGroup:影响进程的CPU资源调度与分配;
procState:从进程所包含的四大组件运行状态来评估进程状态,影响framework的内存控制策略。比如控制缓存进程和空进程个数上限依赖于procState,再比如控制APP执行handleLowMemory()的触发时机等。

CPU调度组:

调度级别缩写解释
SCHED_GROUP_BACKGROUND(0)B后台进程组
SCHED_GROUP_DEFAULT(1)F前台进程组
SCHED_GROUP_TOP_APP(2)TTOP进程组
SCHED_GROUP_TOP_APP_BOUND(3)TTOP进程组
  1. 常说的前台进程与后台进程,其实是从CPU调度角度来划分的前台与后台;为了让用户正在使用的TOP进程能分配到更多的CPU资源,从Android 6.0开始新增了TOP进程组,CPU调度优先分配给当前正在跟用户交互的APP,提升用户体验。
  2. 上图adjType=”broadcast”的CPU调度组的选择取决于广播队列,当receiver接收到的广播来自于前台广播队列则采用前台进程组,当receiver接收到的广播来自于后台广播队列则采用后台进程组。前后台广播队列的CPU资源调度优先级不同,所以前台广播超时10秒就会ANR,而后台广播超时60秒才会ANR。更少的CPU资源分配就需要更长的时间来完成执行,这也就是为何两个广播队列定义了不同的超时阈值。
  3. 上图adjType=”exec-service”的CPU调度组的选择取决于caller, 当发起bindService或者startService的调用者caller属于后台进程组,callerFg=false,则Service的生命周期回调运行在后台进程组,非常少的CPU资源;当caller属于前台或者TOP进程组,则Service的生命周期回调运行在前台进程组,分配较多的CPU资源。
  4. 上图adjType=”service”也有机会选择TOP组, 前提条件是在bindService的时候带有BIND_IMPORTANT的flags,用于标记该服务对于客户端进程很重要。

对于app开发者的建议:

  1. UI进程与Service进程一定要分离,因为对于包含activity的service进程,一旦进入后台就成为”cch-started-ui-services”类型的cache进程(ADJ>=900),随时可能会被系统回收;而分离后的Service进程服务属于SERVICE_ADJ(500),被杀的可能性相对较小。尤其是系统允许自启动的服务进程必须做UI分离,避免消耗系统较大内存。
  2. 只有真正需要用户可感知的应用,才调用startForegroundService()方法来启动前台服务,此时ADJ=PERCEPTIBLE_APP_ADJ(200),常驻内存,并且会在通知栏常驻通知提醒用户,比如音乐播放,地图导航。切勿为了常驻而滥用前台服务,这会严重影响用户体验。
  3. 进程中的Service工作完成后,务必主动调用stopService或stopSelf来停止服务,避免占据内存,浪费系统资源;
  4. 不要长时间绑定其他进程的service或者provider,每次使用完成后应立刻释放,避免其他进程常驻于内存;
  5. APP应该实现接口onTrimMemory()和onLowMemory(),根据TrimLevel适当地将非必须内存在回调方法中加以释放。当系统内存紧张时会回调该接口,减少系统卡顿与杀进程频次。
  6. 减少在保活上花心思,更应该在优化内存上下功夫,因为在相同ADJ级别的情况下,系统会选择优先杀内存占用的进程。

参考:
http://gityuan.com/2018/05/19/android-process-adj/
https://blog.csdn.net/kickxxx/article/details/13996565
https://blog.csdn.net/u012602304/article/details/79066000
https://blog.csdn.net/sinat_34606064/article/details/77932268
http://gityuan.com/2016/09/17/android-lowmemorykiller/

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值