[LMKD] [Android] 进程OomAdj调整分析:OomAdj状态简要(1)

一. 什么是OomAdj

oomAdj是Android系统中的一个进程内存管理参数,它决定了系统在内存不足时回收进程的顺序。oomAdj的值越小,说明该进程越重要,越不容易被系统回收。Android系统会根据进程的oomAdj值来决定哪些进程应该被回收,以达到最大限度地提高系统的稳定性和性能。

二. OomAdj大概分析

可以通过ProcessList.java中的setOomAdj方法去调整进程的优先级, OOM_ADJ (Out-Of-Memory Adjustment),进程优先级影响系统对进程的内存回收策略。OOM_ADJ 值越低,表示该进程的优先级越高,系统回收内存时越不会杀死该进程。OOM_ADJ 值越高,则表示该进程的优先级越低,系统回收内存时越容易杀死该进程

在 Android 中,OOM_ADJ 值的范围为 -1000 到 1000,其中 -1000 表示最高优先级(最不容易被杀死),而 1000 表示最低优先级(最容易被杀死)。具体来说,-1000 到 -900 被认为是"native进程,系统进程";-800 到 0 被认为是"系统常驻进程,前台进程,可见进程,可感知进程等";而 1000 则被认为是"空进程"。

关于 -1000 和 1000 优先级的区别,主要体现在它们所代表的进程类型不同。具体来说:

  • -1000 优先级的进程是属于前台进程,这些进程通常是用户当前正在操作的应用程序。这些进程可以使用大量的系统资源,并且在系统内存不足时也不容易被杀死。因此,系统会尽量保留这些进程的内存,以保证用户体验的流畅性。
  • 1000 优先级的进程是属于空进程,即没有任何应用程序或服务的进程。这些进程不占用任何内存,因此在系统内存不足时,它们往往会被最先杀死。因此,这些进程通常只会被用来占位,或者用来作为一些系统服务的容器。
  • 可以在/proc/pid/oom_score_adj查看当前OomAdj值

三. 进程Adj值说明

| 🍎 进程ADJ值位于**frameworks/base/services/core/java/com/android/server/am/ProcessList.java**中,分从-1000~1000

  • static final int *INVALID_ADJ* = -**10000**:表示无效的adj值,也是个默认未初始化的值
  • static final int *UNKNOWN_ADJ* = **1001**:表示未知adj值,一般来说,这是要缓存的内容,但我们还不知道要分配的缓存范围中的确切值
  • static final int *CACHED_APP_MIN_ADJ* = **900**--->static final int *CACHED_APP_MAX_ADJ* = **999**:表示不可见,不活跃的进程,在任何情况下都可以杀死该进程,该类进程都是标记为cache进程,Empty进程,当Empty进程数量达到一定值,则会主动kill这些进程
  • static final int *CACHED_APP_LMK_FIRST_ADJ* = **950**:优先考虑杀死的进程对象,也是属于Empty进程
  • static final int *CACHED_APP_IMPORTANCE_LEVELS* = **5**:代表每个cache进程的OomAdj值的间隔,如果是同一个进程组内的间隔是紧挨着的,若不是则间隔5(cache进程都是在900~999以内)
  • static final int *SERVICE_B_ADJ* = **800**:服务分为A和B两大类,属于B类的服务没有那么活跃,比较"死寂",容易被杀
  • static final int *PREVIOUS_APP_ADJ* = **700**:如果是上一个进程,而且拥有缓存的activity,adj类型是"previous",例如在两个activity进行切换,那么上一个切换到后台的activity的优先级就是700
  • static final int *HOME_APP_ADJ* = **600**:这是一个只有home应用才有的adj值,通常是launcher应用的一个固定adj值,尽量避免杀死它,即使它通常在后台,因为用户与它的交互太多了,这里值的是不会高于600,如果回到launcher桌面,它的adj值仍然为0(应用所能达到的最低优先级)
  • static final int *SERVICE_ADJ* = **500**:一个进程持有一个服务,可能会是这个值,通常情况:Service的最近活跃时间在30分钟以内,会赋予Service所在进程adj值为SERVICE_ADJ(500),那Service30分钟内没有活跃,则不会是这个adj值。服务在没有任何客户端绑定或调用它的情况下可以保持运行的最长时间,超过这个时间(30分钟),系统将自动停止该服务以释放系统资源
  • static final int *HEAVY_WEIGHT_APP_ADJ* = **400**:一个进程如果任务很繁重,会赋予这个值,这个值在启动时在system/rootdir/init.rc中设置,尽量避免不被杀死,这是一个比较重要的进程,在AndroidManifest.xml设置了"android:cantSaveState="true"",同时系统配置了这个feature,那么其无法保存进程状态,由于无法保存状态,故尽量不要杀死, 一般只有一个,adj类型是"heavy"
  • static final int *BACKUP_APP_ADJ* = **300**:这是当前承载备份操作的进程,具体作用不明
  • static final int *PERCEPTIBLE_LOW_APP_ADJ* = **250**:用户可感知的最低级别,通常在客户端绑定服务端时设置了BIND_NOT_PERCEPTIBLE这个Context.flag,且客户端adj≤PERCEPTIBLE_APP_ADJ(可感知进程200,也就是客户端优先级低的情况,优先级越低,越容易保活)且当前进程的adj>250(该adj)的时候,会给该进程赋予PERCEPTIBLE_LOW_APP_ADJ。简单一点来说,就是客户端adj保活率很高,但是当前进程(服务端)保活率很低,则将当前进程设置一个合理的adj值:250,因为客户端在访问服务端时,不希望服务端被kill
  • static final int *PERCEPTIBLE_MEDIUM_APP_ADJ* = **225**:用户无法感知的进程,不过对于绑定到该服务端的客户端来说,它是可感知的,也就是对服务端对端的客户端来说,是可感知的
  • static final int *PERCEPTIBLE_APP_ADJ* = **200**:可感知进程的最低值,也就是可感知进程的最高保活率,比如当前进程正在后台播放音乐,后台正在打电话
  • public static final int *VISIBLE_APP_ADJ* = **100**:可见进程,经过调试home进程在后台时,就处于这个状态。如果处于远程动画播放状态,则会是这个状态。如果当前进程处于这个状态(自己是客户端),那么对端的服务端,则可能会被设置为该进程状态,是因为客户端对服务端的依赖,调整了服务端的adj
  • public static final int *PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ* = 50:拥有前台服务,而且最近在top顶端 的时间在15s以内或者进程状态起码是PROCESS_STATE_TOP的优先级。
    也就是 :调整最近活跃的前台服务进程,在一段时间内(15s),继续像对待前台应用程序一样对待它。进程被移动到后台,不活跃时的状态,然后15s内在top级别(最前台),会被调整跟前台进程一样的待遇
  • public static final int *FOREGROUND_APP_ADJ* = **0** :当一个应用进程处于前台的时候,会是这个值
  • public static final int *PERSISTENT_SERVICE_ADJ* = **-700**:通常用于Service上,表示该服务是系统级别的常驻进程服务,此类服务被kill后会自启
  • public static final int *PERSISTENT_PROC_ADJ* = **-800**:表示这个是一个常驻进程,持久进程,被kill后会自启,通常是一个系统级别的进程,普通应用想要达到常驻进程状态,需要生命system.uid,代表这是一个系统进程,然后设置persistent标签即可
  • public static final int *SYSTEM_ADJ* = **-900**:代表这是一个系统进程,系统进程默认都是这个adj值,例如system_server是-900,并且不会改变。systemui即属于系统进程,又属于常驻进程,所以他会被分配到常驻进程里:-800
  • public static final int *NATIVE_ADJ* = **-1000**:代表属于一个native进程,非常重要的进程,不会被改变值,例如zygote进程pid是1,那么它属于一个native进程,adj值为-1000

四. 进程状态值说明

| 🍎 进程状态值位于**frameworks/base/core/java/android/app/ActivityManager.java**中,分从-1~20,也是越低越好

  • public static final int *PROCESS_STATE_UNKNOWN* = ProcessStateEnum.*UNKNOWN*;

    进程状态的初始值,进程的未知状态,无效状态,值为-1

  • public static final int *PROCESS_STATE_PERSISTENT* = ProcessStateEnum.*PERSISTENT*;

    最高优先级的进程状态,值为0 ,如果设置了该进程状态,代表这是一个系统进程,持久的系统进程。在oomAdjuster类中,如果客户端绑定服务端带有BIND_ABOVE_CLIENTBIND_IMPORTANT标记,且客户端adj ≤ -800(常驻进程adj),那么服务端会直接赋予最高优先级的进程状态(PROCESS_STATE_PERSISTENT

    再补充一点,在oomAdjuster调整中,会根据getMaxAdj是否≤前台应用adj(0),如果小于0的都是属于系统级别的进程,就会默认给到PROCESS_STATE_PERSISTENT进程状态

    java 复制代码
    if (state.getMaxAdj() <= ProcessList.FOREGROUND_APP_ADJ) {
                // The max adjustment doesn't allow this app to be anything
                // below foreground, so it is not worth doing work for it.
                if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                    reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
                }
                // 小于等于0的进程,都属于比较重要的进程
                // 所以type调整为fixed,为固定不可调整
                state.setAdjType("fixed");
                state.setAdjSeq(mAdjSeq);
                // 将进程之前设置最大adj(在processList#newProcessRecordLocked方法中
                // 会将系统进程设置setMaxAdj(ProcessList.PERSISTENT_PROC_ADJ))
                state.setCurRawAdj(state.getMaxAdj());
                // 默认是没有前景activity(默认值)
                state.setHasForegroundActivities(false);
                // 给的默认分组--分组分为SCHED_GROUP_BACKGROUND,SCHED_GROUP_TOP_APP,这两个比较常见
                state.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
                // 此类进程应用拥有最大的能力,CAMERA,MICROPHONE,NETWORK,LOCATION
                state.setCurCapability(PROCESS_CAPABILITY_ALL);
                // 设置进程状态,系统进程都设置为进程持久化状态
                **state.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);**
                // System processes can do UI, and when they do we want to have
                // them trim their memory after the user leaves the UI.  To
                // facilitate this, here we need to determine whether or not it
                // is currently showing UI.
                // 系统进程默认没有ui界面,也算个初始值
                state.setSystemNoUi(true);
                ...
    }

    客户端≤ -800,肯定是系统级别的进程了,系统级别的进程去调用服务端,服务端的进程状态也被拉到最高

  • public static final int *PROCESS_STATE_PERSISTENT_UI* = ProcessStateEnum.*PERSISTENT_UI*;

    表示该进程属于常驻进程,持久进程,系统进程,不过该系统进程必须有UI界面,值为1 ,例如亮屏状态或者正在执行远程动画状态,那么该进程会被直接赋予PROCESS_STATE_PERSISTENT_UI,提升进程状态为 常驻进程UI级。例如systemui有机会赋予这个进程状态

  • public static final int *PROCESS_STATE_TOP* = ProcessStateEnum.*TOP*;

    该进程状态,值为2 ,表示进程作为当前的顶级activity,即它是当前用户正在与之交互的应用程序。当系统处于内存不足时,系统会尽量避免杀死处于 TOP 状态的应用程序。就算是灭屏,如果该activity仍然是顶级的,也是这个状态,在oomAdj调整策略中,不会给该进程调整到这个进程状态,而是在其他地方赋予的这个进程状态(看样子是在ATMS#updateSleepIfNeededLocked会赋予这个进程状态)

  • public static final int *PROCESS_STATE_BOUND_TOP* = ProcessStateEnum.*BOUND_TOP*;

    该进程状态,值为3 ,表示绑定到top应用的进程,在oomAdj调整策略中,当客户端的adj==PROCESS_STATE_TOP时,会将客户端的进程状态拉到PROCESS_STATE_BOUND_TOP

  • public static final int *PROCESS_STATE_FOREGROUND_SERVICE* = ProcessStateEnum.*FOREGROUND_SERVICE*;

    该进程状态,值为4 ,表示该进程是前台服务进程,运行在前台服务,如下代码,会将前台服务赋予该进程状态

    java 复制代码
    if (psr.hasForegroundServices()) {//是否属于前台服务
        // The user is aware of this app, so make it visible.
        adj = ProcessList.PERCEPTIBLE_APP_ADJ;
        **procState = PROCESS_STATE_FOREGROUND_SERVICE;**
        state.bumpAllowStartFgsState(PROCESS_STATE_FOREGROUND_SERVICE);
        state.setAdjType("fg-service");
        state.setCached(false);
        // 默认分组
        schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
    }
  • public static final int *PROCESS_STATE_BOUND_FOREGROUND_SERVICE*=ProcessStateEnum.*BOUND_FOREGROUND_SERVICE*;

    该进程状态,值为5 ,表示绑定前台服务的进程,也就是该进程由于system的绑定(客户端绑定到服务端),会赋予该进程状态(绑定的前台服务状态)

  • public static final int *PROCESS_STATE_IMPORTANT_FOREGROUND* = ProcessStateEnum.*IMPORTANT_FOREGROUND*;

    该进程状态,值为6,表示这是一个重要的前台进程

    • 一般如果客户端进程状态 < PROCESS_STATE_TOP且客户端发起的绑定不属于前台服务(也没有像对待activity那样对待服务的flag),会在oom调整阶段赋予客户端这个进程状态
    • 如果hasExternalProcessHandles()为真,但服务端进程状态 > PROCESS_STATE_IMPORTANT_FOREGROUND ,会强制赋予这个进程状态
    • 如果进程的hasOverlayUi()返回真,例如下拉状态栏,锁屏界面,下拉通知栏等,也会赋予这个进程状态
  • public static final int *PROCESS_STATE_IMPORTANT_BACKGROUND* = ProcessStateEnum.*IMPORTANT_BACKGROUND*;

    该进程状态,值为7 ,表示这是一个重要的后台进程,对用户来说很重要,例如客户端绑定服务端(ConnectionRecord)带有Context.PROCESS_STATE_IMPORTANT_BACKGROUND 标识,则会赋予客户端这个进程状态

  • public static final [int](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/ActivityManager.java;bpv=1;bpt=1;l=673?q=frameworks%2Fbase%2Fcore%2Fjava%2Fandroid%2Fapp%2FActivityManager.java&ss=android%2Fplatform%2Fsuperproject%2Fmain:frameworks%2F&gsn=int&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%2Fmain%2F%2Fmain%3Flang%3Djava%23int%2523builtin) [PROCESS_STATE_TRANSIENT_BACKGROUND](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/ActivityManager.java;bpv=1;bpt=1;l=673?q=frameworks%2Fbase%2Fcore%2Fjava%2Fandroid%2Fapp%2FActivityManager.java&ss=android%2Fplatform%2Fsuperproject%2Fmain:frameworks%2F&gsn=PROCESS_STATE_TRANSIENT_BACKGROUND&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%2Fmain%2F%2Fmain%3Flang%3Djava%3Fpath%3Dandroid.app.ActivityManager%23828a91473ae3100fdbcfecedf45122b6da99fd42dd14513f60fd42de80186b33) = [ProcessStateEnum](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/ActivityManager.java;bpv=1;bpt=1;l=674?q=frameworks%2Fbase%2Fcore%2Fjava%2Fandroid%2Fapp%2FActivityManager.java&ss=android%2Fplatform%2Fsuperproject%2Fmain:frameworks%2F&gsn=ProcessStateEnum&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%2Fmain%2F%2Fmain%3Flang%3Djava%3Fpath%3Dandroid.app.ProcessStateEnum%235c7affbcd4a8850d9a1fdc7af09e333d86189c9a15b21733f25432cb52c423ee).[TRANSIENT_BACKGROUND](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/ActivityManager.java;bpv=1;bpt=1;l=674?q=frameworks%2Fbase%2Fcore%2Fjava%2Fandroid%2Fapp%2FActivityManager.java&ss=android%2Fplatform%2Fsuperproject%2Fmain:frameworks%2F&gsn=TRANSIENT_BACKGROUND&gs=KYTHE%3A%2F%2Fkythe%3A%2F%2Fandroid.googlesource.com%2Fplatform%2Fsuperproject%2Fmain%2F%2Fmain%3Flang%3Djava%3Fpath%3Dandroid.app.ProcessStateEnum%23676a1715b3502159ced8a831aa96b9273e3e118e21a2667e0e6aee3ef846e1e8);

    该进程状态,值为8,进程短暂的处于后台,因此我们将尝试继续运行,例如以下情况会在oom调整时赋予该进程状态

    • 例如adj > PERCEPTIBLE_APP_ADJ(可感知优先级) && 进程状态 > PROCESS_STATE_TRANSIENT_BACKGROUND,但是该进程状态的getForcingToImportant() ≠ null,代表强制给优先级不高的进程强制变更为正要的进程,会赋予这个进程状态,(调用AMS的setProcessImportant设置,强制提升优先级和进程状态)
    • 如果该进程属于BackupRecord(备份),则尽量避免被杀死,会将赋予这个进程状态值
    • 如果客户端绑定服务端(ConnectionRecord)不带有Context.PROCESS_STATE_IMPORTANT_BACKGROUND 标识,但是进程状态又有点高(值越低),例如clientProcState < PROCESS_STATE_TRANSIENT_BACKGROUND,则会赋予这个进程状态
  • public static final int *PROCESS_STATE_BACKUP* = ProcessStateEnum.*BACKUP*;

    该进程状态,值为9 ,该进程属于备份状态(具体作用不明),进程在后台运行备份/恢复操作会赋予这个进程状态,尽量不免被杀死

    例如能通过app.userId在AMS中获取到BackupRecord,则会赋予Backup_app_adj的adj值,进程状态高于这个值的时候,也会赋予这个进程状态,也就是进程优先级高了(优先级越高,值越低),我管不了高优先级的,但是优先级太低了,我就把你拉到这个进程状态

  • public static final int *PROCESS_STATE_SERVICE* = ProcessStateEnum.*SERVICE*;

    该进程状态,值为10 ,代表进程处于正在运行的前后台服务,也就是只要该进程存在服务,默认就是这个进程状态(后续可能会被调整)。

    oomAdjuster调整中,会根据top-activity,运行远程动画的activity,instrumentation,正在接收的广播,正在运行的服务,会赋予默认的进程状态

  • public static final int *PROCESS_STATE_RECEIVER* = ProcessStateEnum.*RECEIVER*;

    该进程状态,值为11,代表进程处于正在接收广播

  • public static final int *PROCESS_STATE_TOP_SLEEPING* = ProcessStateEnum.*TOP_SLEEPING*;

    该进程状态,值为12,当设备处于休眠状态,会赋予这个进程状态

  • public static final int *PROCESS_STATE_HEAVY_WEIGHT* = ProcessStateEnum.*HEAVY_WEIGHT*;

    该进程状态,值为13,代表进程处于后台,但是该进程无法保存和恢复状态,如AndroidManifest.xml设置了"android:cantSaveState="true"",同时系统配置了这个feature,由于其无法保存状态,故尽量不要杀死,adj类型是"heavy"

  • public static final int *PROCESS_STATE_HOME* = ProcessStateEnum.*HOME*;

    该进程状态,值为14,代表该进程状态属于home进程,也就是桌面的launcher,桌面应用的进程状态不能低于PROCESS_STATE_HOME(14),launcher需要一直运行,无论前后台,也是个持久进程,被kill后会自启。因为和launcher交互的会比较少, 故优先级设置较低

  • public static final int *PROCESS_STATE_LAST_ACTIVITY* = ProcessStateEnum.*LAST_ACTIVITY*;

    该进程状态,值为15,可以理解为该进程处于activity界面,但是按了home或者back回到了后台(activity发生stop时),会赋予这个进程状态。后台进程,在运行最后一次显示的activity,会赋予这个进程状态

  • public static final int *PROCESS_STATE_CACHED_ACTIVITY* = ProcessStateEnum.*CACHED_ACTIVITY*;

    该进程状态,值为16 ,进程正在缓存activity,代表缓存的activity,如果缓存数量达到cachedProcessLimit(受emptyProcessLimity影响,32-emptyProcessLimity = 16)上限,则会主动调用app.killLoced()杀死目前的进程,例如psr.isTreatedLikeActivity()为真且进程状态已经≥PROCESS_STATE_CACHED_EMPTY的情况,代表这个服务像对待activity那样对待它,故会赋予这个进程状态

  • public static final int *PROCESS_STATE_CACHED_ACTIVITY_CLIENT* = ****ProcessStateEnum.*CACHED_ACTIVITY_CLIENT*;

    该进程状态,值为17,所有内容同上,不同的是如果这个服务的客户端具有activity,即hasClientActivities()为真,才会赋予这个进程状态,也是超过16个这样的进程会被kill------>该进程是另一个包含activity进程的客户端,会是这个进程状态

  • public static final int *PROCESS_STATE_CACHED_RECENT* = ProcessStateEnum.*CACHED_RECENT*;

    该进程状态,值为18 ,缓存进程,且有一个activity是最近任务里的activity,具体被kill的逻辑也同上.

    有activity在最近任务mRecentTasks中,代表最近启动的activity,且当前进程状态>该进程状态,会设置为该进程状态

  • public static final int *PROCESS_STATE_CACHED_EMPTY* = ProcessStateEnum.*CACHED_EMPTY*;

    该进程状态,值为19 ,代表一个空进程,也是被缓存了的进程,当该状态的进程数量超越了ActivityManagerConstants#CUR_TRIM_EMPTY_PROCESSES---emptyProcessLimity上限(32/2=16个进程),且进程30分钟内没有活跃,则会主动调用app.killLoced()杀死目前的进程,原因就是存在空进程时间太长,被kil,实现就在OomAdjuster.java中------当然也可以不用等30分钟,也可以等待进程上限,也是16个(受ram不同,上限也不同,可客制化),原因:空进程存在的太多了,可以在这客制化上限数量

  • public static final int *PROCESS_STATE_NONEXISTENT* = ProcessStateEnum.*NONEXISTENT*;

    该进程状态,值为20,代表进程不存在,算个初始值,进程销毁期间也是这个值

五. 进程分组说明

| 🍎 进程分组值位于**frameworks/base/core/java/android/app/ActivityManager.java**中,分从0~4,值越高能力越强

  • static final int *SCHED_GROUP_BACKGROUND* = 0;
  • static final int *SCHED_GROUP_RESTRICTED* = 1;
  • static final int *SCHED_GROUP_DEFAULT* = 2;
  • public static final int *SCHED_GROUP_TOP_APP* = 3;
  • static final int *SCHED_GROUP_TOP_APP_BOUND* = 4;
相关推荐
学会沉淀。2 分钟前
Docker学习
java·开发语言·学习
如若1233 分钟前
对文件内的文件名生成目录,方便查阅
java·前端·python
吃着火锅x唱着歌17 分钟前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
初晴~34 分钟前
【Redis分布式锁】高并发场景下秒杀业务的实现思路(集群模式)
java·数据库·redis·分布式·后端·spring·
黑胡子大叔的小屋1 小时前
基于springboot的海洋知识服务平台的设计与实现
java·spring boot·毕业设计
ThisIsClark1 小时前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
_Shirley2 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
雷神乐乐2 小时前
Spring学习(一)——Sping-XML
java·学习·spring
小林coding3 小时前
阿里云 Java 后端一面,什么难度?
java·后端·mysql·spring·阿里云
V+zmm101343 小时前
基于小程序宿舍报修系统的设计与实现ssm+论文源码调试讲解
java·小程序·毕业设计·mvc·ssm