本文基于原生Android 9.0源码来解读进程优先级原理,基于篇幅考虑会精炼部分代码
Android框架对进程创建与管理进行了封装,对于APP开发者只需知道Android四大组件的使用。当Activity, Service, ContentProvider, BroadcastReceiver任一组件启动时,当其所承载的进程存在则直接使用,不存在则由框架代码自动调用startProcessLocked创建进程。一个APP可以拥有多个进程,多个APP也可以运行在同一个进程,通过配置Android:process属性来决定。所以说对APP来说进程几乎是透明的,但了解进程对于深刻理解Android系统是至关关键的。
Android系统的设计理念正是希望应用进程能尽量长时间地存活,以提升用户体验。应用首次打开比较慢,这个过程有进程创建以及Application等信息的初始化,所以应用在启动之后,即便退到后台并非立刻杀死,而是存活一段时间,这样下次再使用则会非常快。对于APP同样希望自身尽可能存活更长的时间,甚至探索各种保活黑科技。物极必反,系统处于低内存的状态下,手机性能会有所下降;系统继续放任所有进程一直存活,系统内存很快就会枯竭而亡,那么需要合理地进程回收机制。
到底该回收哪个进程呢?系统根据进程的组件状态来决定每个进程的优先级值ADJ,系统根据一定策略先杀优先级最低的进程,然后逐步杀优先级更低的进程,依此类推,以回收预期的可用系统资源,从而保证系统正常运转。
谈到优先级,可能有些人会想到Linux进程本身有nice值,这个能决定CPU资源调度的优先级;而本文介绍Android系统中的ADJ,主要决定在什么场景下什么类型的进程可能会被杀,影响的是进程存活时间。ADJ与nice值两者定位不同,不过也有一定的联系,优先级很高的进程,往往也是用户不希望被杀的进程,是具有有一定正相关性。
ADJ级别 | 取值 | 含义 |
---|---|---|
NATIVE_ADJ | -1000 | native进程 |
SYSTEM_ADJ | -900 | 仅指system_server进程 |
PERSISTENT_PROC_ADJ | -800 | 系统persistent进程 |
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 | 重量级进程 |
SERVICE_ADJ | 500 | 服务进程 |
HOME_APP_ADJ | 600 | Home进程 |
PREVIOUS_APP_ADJ | 700 | 上一个进程 |
SERVICE_B_ADJ | 800 | B List中的Service |
CACHED_APP_MIN_ADJ | 900 | 不可见进程的adj最小值 |
CACHED_APP_MAX_ADJ | 906 | 不可见进程的adj最大值 |
从Android 7.0开始,ADJ采用100、200、300;在这之前的版本ADJ采用数字1、2、3,这样的调整可以更进一步地细化进程的优先级,比如在VISIBLE_APP_ADJ(100)与PERCEPTIBLE_APP_ADJ(200)之间,可以有ADJ=101、102级别的进程。
省去lmk对oom_score_adj的计算过程,Android 7.0之前的版本,oom_score_adj= oom_adj * 1000/17; 而Android 7.0开始,oom_score_adj= oom_adj,不用再经过一次转换。
为了防止剩余内存过低,Android在内核空间有lowmemorykiller(简称LMK),LMK是通过注册shrinker来触发低内存回收的,这个机制并不太优雅,可能会拖慢Shrinkers内存扫描速度,已从内核4.12中移除,后续会采用用户空间的LMKD + memory cgroups机制,这里先不展开LMK讲解。
进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。下图参数为Android原生阈值,当系统剩余空闲内存低于某阈值(比如147MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。 如下是64位机器,LMK默认阈值图:
在updateOomLevels()过程,会根据手机屏幕尺寸或内存大小来调整scale,默认大多数手机内存都大于700MB,则scale等于1。对于64位手机,阈值会更大些,具体如下。
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
...
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
}
接下来,解读每个ADJ值都对应着怎样条件的进程,包括正在运行的组件以及这些组件的状态几何。这里重点介绍上图标红的ADJ级别所对应的进程。
Android系统中计算各进程ADJ算法的核心方法:
当Android四大组件状态改变时会updateOomAdjLocked()来同步更新相应进程的ADJ优先级。这里需要说明一下,当同一个进程有多个决定其优先级的组件状态时,取优先级最高的ADJ作为最终的ADJ。另外,进程会通过设置maxAdj来限定ADJ的上限。
关于分析进程ADJ相关信息,常用命令如下:
再来说一下其他优先级:
SYSTEM_ADJ: 仅指system_server进程。在执行SystemServer的startBootstrapServices()过程会调用AMS.setSystemProcess(),将system_server进程的maxAdj设置成SYSTEM_ADJ,源码如下:
public void setSystemProcess() {
...
ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
"android", STOCK_PM_FLAGS | MATCH_SYSTEM_ONLY);
mSystemThread.installSystemApplicationInfo(info, getClass().getClassLoader());
synchronized (this) {
ProcessRecord app = newProcessRecordLocked(info, info.processName, false, 0);
app.persistent = true;
app.pid = MY_PID;
app.maxAdj = ProcessList.SYSTEM_ADJ;
app.makeActive(mSystemThread.getApplicationThread(), mProcessStats);
synchronized (mPidsSelfLocked) {
mPidsSelfLocked.put(app.pid, app);
}
updateLruProcessLocked(app, false, null);
updateOomAdjLocked();
}
...
}
但system_server的ADJ并非等于-900,而是-800?是由于startPersistentApps()过程直接把其adj重新被设置为-800,这算是一个小BUG,但 其实目前来说对于ADJ<0的进程,LMK不会杀,两者没有什么区别。
PERSISTENT_PROC_ADJ:在AndroidManifest.xml中申明android:persistent=”true”的系统(即带有FLAG_SYSTEM标记)进程,称之为persistent进程。对于persistent进程常规情况都不会被杀,一旦被杀或者发生Crash,进程会立即重启。
AMS.addAppLocked()或AMS.newProcessRecordLocked()过程会赋值:
场景1: newProcessRecordLocked
final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, boolean isolated, int isolatedUid) {
String proc = customProcess != null ? customProcess : info.processName;
final int userId = UserHandle.getUserId(info.uid);
int uid = info.uid;
...
final ProcessRecord r = new ProcessRecord(stats, info, proc, uid);
if (!mBooted && !mBooting
&& userId == UserHandle.USER_SYSTEM
&& (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
r.persistent = true;
r.maxAdj = ProcessList.PERSISTENT_PROC_ADJ;
}
if (isolated && isolatedUid != 0) {
r.maxAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
}
return r;
}
在每一次进程启动的时候都会判断该进程是否persistent进程,如果是则会设置maxAdj=PERSISTENT_PROC_ADJ。 system_server进程应该也是persistent进程?
场景2:addAppLocked
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated, String abiOverride) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
info.uid, true)<