【Oomadjuster项目问题实战】launcher oomadj老化导致被kill

一 通过阅读可以收获以下知识

  1. launcher进程在后台oomadj为什么一会儿500、一会儿600、还会变成800被杀掉
  2. oomadjType为service类型进程的老化的过程、
  3. service进程老化条件

二 问题背景

  1. launcher在退回后台后,有时候oomadj是600,有时候是500.应用侧不清楚原因。
  2. 当launcher在后台时会被kill,当按Home键或者back键再次启动时,进程重启,由于此时采用第一次启动时的intent携带参数恢复,导致launcher状态异常,因此需要避免launch被kill,经过排查launcher被soc kill,因为oomadj为800,众所周知launcher的oomadj为600,那么为什么被kill时会是800?

针对以上两个问题,以下做出解释。先科普一下oomadj 500、600、800的区别

    // The B list of SERVICE_ADJ -- these are the old and decrepit
    // services that aren't as shiny and interesting as the ones in the A list.
    static final int SERVICE_B_ADJ = 800;

    // This is a process holding the home application -- we want to try
    // avoiding killing it, even if it would normally be in the background,
    // because the user interacts with it so much.
    static final int HOME_APP_ADJ = 600;
    
    // This is a process holding an application service -- killing it will not
    // have much of an impact as far as the user is concerned.
    static final int SERVICE_ADJ = 500;

500:service类型的进程

600:home属性的进程

800:老化的service类型进程

三 问题排查

3.1 为什么launch的oomadj会变成800,800是属于什么类型。

从注释可以看到,800是老化service的oomadj值,接下来找800赋值的地方。

// === 当前adj必须是500 ===
if (adj == ProcessList.SERVICE_ADJ) {
    if (doingAll && !cycleReEval) {
// === service 是否在队列的前1/3 ,是则老化 === 
        state.setServiceB(mNewNumAServiceProcs > (mNumServiceProcs / 3));
        mNewNumServiceProcs++;
// === 进程service没老化 === 
        if (!state.isServiceB()) {
            // This service isn't far enough down on the LRU list to
            // normally be a B service, but if we are low on RAM and it
            // is large we want to force it down since we would prefer to
            // keep launcher over it.
// === 通过上面的注释可以看到,虽然进程没有老化,但是当前处于低内存状态,并且此进程占用pss超过水位线,也会强制老化 ===
            if (!mService.mAppProfiler.isLastMemoryLevelNormal()
                    && app.mProfile.getLastPss()
                    >= mProcessList.getCachedRestoreThresholdKb()) {
                state.setServiceHighRam(true);
                state.setServiceB(true);
                //Slog.i(TAG, "ADJ " + app + " high ram!");
            } else {
                mNewNumAServiceProcs++;
                //Slog.i(TAG, "ADJ " + app + " not high ram!");
            }
        } else {
            state.setServiceHighRam(false);
        }
    }
// == 当前进程service已经老化 ==
    if (state.isServiceB()) {
// === 这里是赋值800的地方,整个AOSP仅此一处会赋值800. ===
        adj = ProcessList.SERVICE_B_ADJ;
    }
}

从if判断可知,launcher的oomadj想要变成800,首先必须当前是ProcessList.SERVICE_ADJ(500)。

oomadj计算adj值的函数中,从上到下会根据不同的情况计算很多次,如果计算出的500在后面,因此会覆盖前面的600.

通过dumpsys activity p +launcher包名

看到launcher进程中确实存在service。因此,系统侧建议应用将service剥离到独立进程执行业务逻辑,就像Google launcherx,service属于单独的进程。但是由于剥离业务过于复杂不适合,最终采用soc添加白名单避免被kill的方式解决。


3.2 launch在退到后台时,有时候oomadj为500、有时候为600,有时候必现500、有时候必现600.

过了不久,应用发现新的问题,launch在退到后台时,有时候oomadj为500、有时候为600,有时候必现500、有时候必现600.

600为HOME属性的oomadj可以理解,但是为什么会变成500呢?这里就需要查找500赋值的地方。

[Oomadjuster.java]

for (int is = psr.numberOfRunningServices() - 1;
        is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                || procState > PROCESS_STATE_TOP);
        is--) {
// === 省略 ===
                    } else {
                    if (s.mKeepWarming
                            || now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
                        // This service has seen some activity within
                        // recent memory, so we will keep its process ahead
                        // of the background processes.
                        if (adj > ProcessList.SERVICE_ADJ) {
// === 这里是赋值500的地方 ===
                            adj = ProcessList.SERVICE_ADJ;
                            state.setAdjType("started-services");
                            if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
                                reportOomAdjMessageLocked(TAG_OOM_ADJ,
                                        "Raise adj to started service: " + app);
                            }
                            state.setCached(false);
                        }
                    }
                    // If we have let the service slide into the background
                    // state, still have some text describing what it is doing
                    // even though the service no longer has an impact.
                    if (adj > ProcessList.SERVICE_ADJ) {
                        state.setAdjType("cch-started-services");
                    }
                }

全局检索赋值500的地方,仅发现一处如上,解释一下这段逻辑。

oomadj计算时会遍历此进程中所有发布的service,并且通过以下公式计算是否是service类型的oomadj

if (s.mKeepWarming
|| now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
解释:如果当前oomadj触发的时间now < (service发布时间+30min),则为进程赋值oomadj类型为500,标识这是service类型的进程。

结合业务场景,可以知道,当launcher退到后台时,遍历launcher进程的service,发现此时小于service创建时间的30min内,则会为launcher赋值oomadj 500。

根据判断条件可知,当launch中没有新创建的service且过去30分钟后,则无法触发if条件,就不会赋值500,adj值保持600,因此得出以下场景:

  1. 冷开机后,从launcher立即进入其他应用中,发现launcher此时在后台的oomadj是500
  2. 冷开机后,在launcher静止30分钟,再进入其他三方应用,发现launcher此时在后台的oomadj为600
  3. 需要注意的是并非静止30分钟后,进入后台oomadj一定为600,如果launch此时启动了新的service,再次退到后台,依旧会变500

service进程oomadj的老化过程500->800

先看一下oomadj service老化代码:

// === 当前adj必须是500 ===
if (adj == ProcessList.SERVICE_ADJ) {
    if (doingAll && !cycleReEval) {
// === service 是否在队列的前1/3 ,是则老化 === 
        state.setServiceB(mNewNumAServiceProcs > (mNumServiceProcs / 3));
        mNewNumServiceProcs++;
// === 进程service没老化 === 
        if (!state.isServiceB()) {
            // This service isn't far enough down on the LRU list to
            // normally be a B service, but if we are low on RAM and it
            // is large we want to force it down since we would prefer to
            // keep launcher over it.
// === 通过上面的注释可以看到,虽然进程没有老化,但是当前处于低内存状态,并且此进程占用pss超过水位线,也会强制老化 ===
            if (!mService.mAppProfiler.isLastMemoryLevelNormal()
                    && app.mProfile.getLastPss()
                    >= mProcessList.getCachedRestoreThresholdKb()) {
                state.setServiceHighRam(true);
                state.setServiceB(true);
                //Slog.i(TAG, "ADJ " + app + " high ram!");
            } else {
                mNewNumAServiceProcs++;
                //Slog.i(TAG, "ADJ " + app + " not high ram!");
            }
        } else {
            state.setServiceHighRam(false);
        }
    }
// == 当前进程service已经老化 ==
    if (state.isServiceB()) {
// === 这里是赋值800的地方,整个AOSP仅此一处会赋值800. ===
        adj = ProcessList.SERVICE_B_ADJ;
    }
}

当前oomadj必须是500,进程的service老化,分为两种情况:

  1. 当前进程的service在整个service队列中,处于最老的1/3 时老化
  2. 当前整机的内存因子不为nomal且当前进程占用的pss大于水位线时老化
    1. nomal为4个内存压力因子其中一个
    2. 水位线为不同设备配置的不同标准

3.3 内存压力因子介绍:

public static final int ADJ_MEM_FACTOR_NORMAL = 0;
public static final int ADJ_MEM_FACTOR_MODERATE = 1;
public static final int ADJ_MEM_FACTOR_LOW = 2;
public static final int ADJ_MEM_FACTOR_CRITICAL = 3;

整机内存压力因子会根据lmkd中获取到内核psi的值来分为4档:

  1. ADJ_MEM_FACTOR_NORMAL 正常
  2. ADJ_MEM_FACTOR_MODERATE 风险水平适中
  3. ADJ_MEM_FACTOR_LOW 低内存
  4. ADJ_MEM_FACTOR_CRITICAL 内存危机

3.4 水位线介绍

当进程处于cached状态系统可以接受的pss值的水位线。

    /**
     * Return the maximum pss size in kb that we consider a process acceptable to
     * restore from its cached state for running in the background when RAM is low.
     */
    long getCachedRestoreThresholdKb() {
        return mCachedRestoreLevel;
    }

    // The maximum size we will restore a process from cached to background, when under
    // memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
    // before killing background processes.
// === ProcessList.CACHED_APP_MAX_ADJ为900 ===
    mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024) / 3;
// === 900 ===
long getMemLevel(int adjustment) {
    for (int i = 0; i < mOomAdj.length; i++) {
        if (adjustment <= mOomAdj[i]) {
            return mOomMinFree[i] * 1024;
        }
    }
// 返回最大的值*1024
    return mOomMinFree[mOomAdj.length - 1] * 1024;
}

这里取mOomMinFreeHigh最大值也就是184320.

    private final int[] mOomMinFreeLow = new int[] {
            12288, 18432, 24576,
            36864, 43008, 49152
    };
	private final int[] mOomMinFreeHigh_2G = new int[] {
            20480, 40960, 61440,
            81920, 102400, 122880
    };
    private final int[] mOomMinFreeHigh_3G = new int[] {
            30720, 61440, 92160,
            122880, 153600, 184320
    };
    private final int[] mOomMinFreeHigh = new int[] {
            73728, 92160, 110592,
            129024, 147456, 184320
    };

不同内存大小机芯可以配置不同的水位线。

​ mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ) / 1024) / 3;

(184320*1024/1024)/3 = 61440 kb = 60mb 也就是说水位线为60mb。

问题单中,launcher在后台时

06-07 11:07:05.863 669 788 I am_pss : [29679,1000,com.tcl.cyberui,103635968,95645696,366592,170520576,0,10,12]

  1. launcher在后台的pss占用103635968 /1024/1024= 98mb > 60mb.
  2. 当前系统内存压力因子factor为2:ADJ_MEM_FACTOR_LOW大于nomal

因此老化条件成立,launcher的oomadj被设置800.随后soc的杀进程机制触发,杀死launcher。

总结:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值