12.2 OOM adj机制

OOM adj(Out Of Memory adjustment)是内存不足状态的调整级别,系统根据进程运行时占有内存和CPU等情况为每个进程计算出一个adj值,该值的取值范围为-17到+15,adj值越大的进程越容易被“杀死”,可以通过cat/proc/<pid>/oom_adj命令查看指定进程的OOM adj值。

为方便归类不同的进程,Android在框架层显式定义了13个调整级别,位于frameworks/base/services/java/com/android/server/am/ProcessList.java中,代码如下:


class ProcessList{

/*后台隐藏的应用程序进程,运行于该进程的Activity是不可见的,“杀死”

该进程并不会对用户体验有较大影响,其adj取值为9~15/

static final int HIDDEN_APP_MAX_ADJ=15;

static int HIDDEN_APP_MIN_ADJ=9;

//应用程序Service的OOM adj分为两类:A类和B类。A类比B类更重要

static final int SERVICE_B_ADJ=8;

/*前一个应用程序所在进程,例如点击E-mail中的URI进入browser,然后

*按back键返回到E-mail,此时E-mail便是前一个应用程序。此外,在最近

两个Task中切换时,也会出现前一个应用程序/

static final int PREVIOUS_APP_ADJ=7;

//运行Home的应用程序进程

static final int HOME_APP_ADJ=6;

//应用程序Service的adj,这里是A类

static final int SERVICE_ADJ=5;

//执行backup操作的进程

static final int BACKUP_APP_ADJ=4;

//运行了heavy-weight应用程序的进程,第10章已提过

static final int HEAVY_WEIGHT_APP_ADJ=3;

//不可见,但运行了用户可以感知的组件的应用程序进程,例如在后台运行的音乐播放器

static final int PERCEPTIBLE_APP_ADJ=2;

//运行了可见Activity,但并不在前台显示的应用程序进程

static final int VISIBLE_APP_ADJ=1;

//当前前台正在运行的应用程序所在的进程

static final int FOREGROUND_APP_ADJ=0;

//系统persistent进程,例如telephony等,第9章已经提过

static final int PERSISTENT_PROC_ADJ=-12;

//system进程,第10章已经提过

static final int SYSTEM_ADJ=-16;

……


Android提供了updateOomAdjLocked方法更新OOM adj值,接下来分析这个方法。

12.2.1 更新OOM adj值

updateOomAdjLocked有3个重载方法,分别如下:

updateOomAdjLocked()

updateOomAdjLocked(ProcessRecord)

updateOomAdjLocked(ProcessRecord,……)

updateOomAdjLocked方法的入口点很多,这里以updateOomAdjLocked()为例,继续上一节的内容,分析更新OOM adj值的执行过程。

updateOomAdjLocked()的执行流程比较复杂,接下来分阶段开始分析。

1.updateOomAdjLocked()第一阶段

updateOomAdjLocked()第一阶段的代码如下:


public final class ActivityManagerService extends ActivityManagerNative

implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback{

……

final void updateOomAdjLocked(){

/*TOP_ACT是当前系统正在处理的Activity,可以是前台显示的Activity或

Pausing状态的Activity,以及栈顶第一个非finishing的Activity/

final ActivityRecord TOP_ACT=resumedAppLocked();

final ProcessRecord TOP_APP=TOP_ACT!=null?TOP_ACT.app:null;

mAdjSeq++;//adj序列号加1

mNewNumServiceProcs=0;

/后台隐藏应用程序的OOM adj取值是9~15,可以取7个值,即numSlots,每个值是一个slot/

int numSlots=ProcessList.HIDDEN_APP_MAX_ADJ-

ProcessList.HIDDEN_APP_MIN_ADJ+1;

/*不同进程可以有相同的adj,这里计算每个slot平均可以容纳的进程

数,即factor。减4的意义不太明确,可能是一个达到最优平衡的经验值/

int factor=(mLruProcesses.size()-4)/numSlots;

if(factor<1)factor=1;

int step=0;

int numHidden=0;

int numTrimming=0;

int i=mLruProcesses.size();

//当前slot从隐藏应用程序的最小adj开始

int curHiddenAdj=ProcessList.HIDDEN_APP_MIN_ADJ;

while(i>0){

i—;

ProcessRecord app=mLruProcesses.get(i);

//调用重载方法更新OOM adj

updateOomAdjLocked(app, curHiddenAdj, TOP_APP, true);

if(curHiddenAdj<ProcessList.HIDDEN_APP_MAX_ADJ

&&app.curAdj==curHiddenAdj){

step++;//如果当前adj值并未改变,则step加1

if(step>=factor){//超过了每个slot可以容纳的平均进程数

step=0;//slot归0

/*当前slot更改为下一个adj级别,之后更新OOM adj的进程

只能使用更大的adj,但不能超过HIDDEN_APP_MAX_ADJ/

curHiddenAdj++;

}

}

if(!app.killedBackground){//是否“杀死”后台隐藏应用程序进程

if(app.curAdj>=ProcessList.HIDDEN_APP_MIN_ADJ){

numHidden++;//增加后台隐藏进程计数

//如果后台隐藏进程数超过了系统限制(默认15个),则需要“杀死”该进程

if(numHidden>mProcessLimit){

Slog.i(TAG,"No longer want"+app.processName+

"(pid"+app.pid+"):hidden#"+numHidden);

app.killedBackground=true;//标记该进程是后台“杀死”的

Process.killProcessQuiet(app.pid);//“杀死”进程

}

}

//如果是isolated进程并且不包含应用程序Service,也要“杀死”

if(!app.killedBackground&&app.isolated

&&app.services.size()<=0){

app.killedBackground=true;

Process.killProcessQuiet(app.pid);

}

if(app.nonStoppingAdj>=ProcessList.HOME_APP_ADJ

&&app.nonStoppingAdj!=ProcessList.SERVICE_B_ADJ

&&!app.killedBackground){

numTrimming++;

}

}

}

……省略后续阶段的内容


updateOomAdjLocked()方法第一阶段的主要工作如下:

计算HIDDEN_APP_MIN_ADJ到HIDDEN_APP_MAX_ADJ可选的adj值(slot),并计算每个slot可以容纳(mLruProcesses-4)进程的平均数。这里的4可能是一个经验值,其具体含义不得而知。

从后往前遍历mLruProcesses,并调用updateOomAdjLocked(ProcessRecord,……)更新每个进程的OOM adj值。

判断后台进程数是否超过HIDDEN_APP_MIN_ADJ的限定值(默认为15),如果超过,则“杀死”该进程。如果是isolated进程并且不包含应用程序Service,也要“杀死”。

2.updateOomAdjLocked()第二阶段

updateOomAdjLocked()第二阶段的主要工作由updateOomAdjLocked(ProcessRecord,……)方法完成,代码如下:


private final boolean updateOomAdjLocked(ProcessRecord app,

int hiddenAdj, ProcessRecord TOP_APP, boolean doingAll){

app.hiddenAdj=hiddenAdj;//以hiddenAdj为基准adj

if(app.thread==null){

return false;

}

//本次计算adj,是否保留该进程,不予“杀死”。该值在上次计算adj时得到

final boolean wasKeeping=app.keeping;

boolean success=true;

//计算OOM adj值,该方法十分复杂

computeOomAdjLocked(app, hiddenAdj, TOP_APP, false, doingAll);

//当前初始adj与上次初始adj值不同

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

/*计算OOm adj值后,app.keeping得到的是本次的计算结果,如果上次计

算结果和本次计算结果至少有一个为false,则该进程可能被“杀死”/

if(wasKeeping&&!app.keeping){

……//获取该进程持有wake lock的时间

……//获取该进程占用CPU的时间

}

app.setRawAdj=app.curRawAdj;//设置上次初始adj值

}

//当前adj值与上次adj值不同,需要修改进程的adj值

if(app.curAdj!=app.setAdj){

if(Process.setOomAdj(app.pid, app.curAdj)){

app.setAdj=app.curAdj;

}else{

success=false;

}

}

if(app.setSchedGroup!=app.curSchedGroup){//如果调度组改变

app.setSchedGroup=app.curSchedGroup;

//后台进程等待被杀死,并且其调度组为后台非活动组

if(app.waitingToKill!=null&&app.setSchedGroup==

Process.THREAD_GROUP_BG_NONINTERACTIVE){

app.killedBackground=true;//标记该进程是后台“杀死”的

Process.killProcessQuiet(app.pid);//“杀死”进程

success=false;

}else{

if(true){

long oldId=Binder.clearCallingIdentity();

try{

//修改进程和其子线程的调度组

Process.setProcessGroup(app.pid, app.curSchedGroup);

}

……

}else{

if(app.thread!=null){

try{

//通过主线程设置进程的调度组,仍然调用setProcessGroup方法

app.thread.setSchedulingGroup(app.curSchedGroup);

}

……

return success;

}


updateOomAdjLocked(ProcessRecord,……)方法的主要工作如下:

调用computeOomAdjLocked方法计算进程的OOM adj值。

调用setOomAdj方法修改进程的OOM adj值。

调用killProcessQuiet“杀死”等待被“杀死”的进程。

调用setProcessGroup方法修改进程的调度组。

computeOomAdjLocked方法的执行流程非常复杂,将在后续小节单独分析。setProcessGroup方法涉及Android的调度策略,其主要定义位于/system/core/include/cutils/sched_policy.h和/system/core/libcutils/sched_policy.c两个文件中,感兴趣的读者可以参照Linux相关调度策略自行分析。接下来分析两个关键方法setOomAdj和killProcessQuiet。

(1)setOomAdj

setOomAdj方法定义于Process.java中,是一个Native方法,其JNI实现方法位于android_util_Process.cpp中,代码如下:


jboolean android_os_Process_setOomAdj(JNIEnv*env, jobject clazz,

jint pid, jint adj)

{

ifdef HAVE_OOM_ADJ

char text[64];

//text设置为/proc/<pid>/oom_adj

sprintf(text,"/proc/%d/oom_adj",pid);

int fd=open(text, O_WRONLY);//打开文件

if(fd>=0){

//text设置为adj

sprintf(text,"%d",adj);

//向/proc/<pid>/oom_adj中写入adj值

write(fd, text, strlen(text));

close(fd);

}

return true;

endif

return false;

}


可见setOomAdj方法将计算出的OOM adj写入进程的/proc/<pid>/oom_adj文件中。在Linux操作系统中,每一个运行的进程都对应一个/proc/<pid>/oom_adj文件。用户空间可以向该文件写入OOM adj值,以控制内存不足时是否“杀死”该进程。

注意 proc/<pid>/目录的具体作用可以参考Linux程序员手册:http://www.kernel.org/doc/man-pages/online/pages/man5/proc.5.html。

(2)killProcessQuiet

killProcessQuiet和killProcess都是“杀死”进程的方法,两者唯一的区别是killProcess方法会在“杀死”进程前打印Log信息,而killProcessQuiet方法则不会打印Log信息。killProcessQuiet定义于Process.java中,代码如下:


public static final void killProcessQuiet(int pid){

sendSignalQuiet(pid, SIGNAL_KILL);//发送SIGNAL_KILL信号

}


SIGNAL_KILL的值为9,是Linux操作系统中强制“杀死”进程的信号,该信号不能被捕获或者忽略。sendSignalQuiet方法是一个Native方法,其JNI实现方法位于android_util_Process.cpp中,代码如下:


void android_os_Process_sendSignalQuiet(JNIEnv*env, jobject clazz,

jint pid, jint sig)

{

if(pid>0){

//通过kill系统调用“杀死”指定进程,该函数定义于头文件<sys/types.h>中

kill(pid, sig);

}

}


可见sendSignalQuiet只是通过系统函数向所在进程发送一个强制退出的信号。

注意 关于kill系统调用和信号的具体作用,可以参考Linux程序员手册:

http://www. kernel.org/doc/man-pages/online/pages/man2/kill.2.html

http://www. kernel.org/doc/man-pages/online/pages/man2/signal.2.html

3.updateOomAdjLocked()第三阶段

updateOomAdjLocked(ProcessRecord,……)方法执行完毕后,返回updateOomAdjLocked()方法,开始第三阶段的工作,代码如下:


public final class ActivityManagerService extends ActivityManagerNative

implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback{

……

final void updateOomAdjLocked(){

……//省略前面阶段的内容

mNumServiceProcs=mNewNumServiceProcs;

//当Hidden进程的个数不大于最大Hidden进程限制的一半时

if(numHidden<=(ProcessList.MAX_HIDDEN_APPS/2)){

final int N=mLruProcesses.size();

……

/*此处的代码比较混乱,还不完善。其核心流程是计算此时后台进程

*的memory trimming level值,该值的计算过程会参考mLruProcesses

*中进程的nonStoppingAdj值和numHidden值,最终以计算出的level值

调用app.thread.scheduleTrimMemory(curLevel)/

}else{//对应Hidden进程的个数大于最大Hidden进程限制的一半时

final int N=mLruProcesses.size();

for(i=0;i<N;i++){

ProcessRecord app=mLruProcesses.get(i);

if((app.nonStoppingAdj>ProcessList.VISIBLE_APP_ADJ||

app.systemNoUi)&&app.pendingUiClean){

if(app.trimMemoryLevel<

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN&&

app.thread!=null){

try{

//同样调用app.thread.scheduleTrimMemory

app.thread.scheduleTrimMemory(

ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);

}catch(RemoteException e){

}

}

app.pendingUiClean=false;

}

app.trimMemoryLevel=0;

}

}

//在Settings的Developer options中提供了是否总是销毁Activity的开关

if(mAlwaysFinishActivities){

//销毁后台Activity

mMainStack.scheduleDestroyActivities(null, false,"always-finish");

}

}


updateOomAdjLocked()方法第三阶段的主要工作分为以下两部分:

计算应用程序的MT Level,进而调用scheduleTrimMemory方法通知应用程序释放内存。

根据Settings的设置参数,决定是否销毁后台Activity。

接下来分析这两部分内容。

(1)调用scheduleTrimMemory方法通知应用程序释放内存

MT Level(Memory Trimming Level,内存整理级别)定义于ComponentCallbacks2中,这是一个回调接口,Activity、应用程序Service、Application、Content Provider、Fragment均实现了该接口。ComponentCallbacks2定义了7种MT Level,应用程序可以根据不同的Level实现onTrimMemory(Level)方法进行细粒度的内存管理操作。

注意 Android开发者网站对不同Level有详细解释,可参考如下资料:

http://developer. android.com/reference/android/content/ComponentCallbacks2.html

MT Level计算完毕后,调用app.thread.scheduleTrimMemory通知应用程序释放内存,app.thread返回的是ApplicationThreadProxy(BinderProxy),最终会通过Binder通信,调用ApplicationThread的scheduleTrimMemory方法,代码如下:


public final class ActivityThread{

……

private class ApplicationThread extends ApplicationThreadNative{

……

public void scheduleTrimMemory(int level){

//向ActivityThread的消息循环中发送TRIM_MEMORY

queueOrSendMessage(H.TRIM_MEMORY, null, level);

}

}


ActivityThread的消息处理器H会在handleMessage方法中处理TRIM_MEMORY消息,代码如下:


public final class ActivityThread{

……

private class H extends Handler{

public void handleMessage(Message msg){

switch(msg.what){

case TRIM_MEMORY:

handleTrimMemory(msg.arg1);

break;

……

}

}


handleTrimMemory方法定义于ActivityThread中,代码如下:


final void handleTrimMemory(int level){

final WindowManagerImpl windowManager=WindowManagerImpl. getDefault();/通过Window Manager回收hardware surfaces和resources,不在本书中分析/

windowManager. startTrimMemory(level);//查询实现了ComponentCallbacks2接口的组件

ArrayList<ComponentCallbacks2>callbacks;synchronized(mPackages){

callbacks=collectComponentCallbacksLocked(true, null);}

//回调各个组件的onTrimMemory方法

final int N=callbacks. size();

for(int i=0;i<N;i++){

callbacks. get(i).onTrimMemory(level);}

windowManager. endTrimMemory();}


handleTrimMemory会循环回调各个组件的onTrimMemory方法,进行细粒度的内存整理操作。但此操作会影响系统和应用程序的性能。

(2)利用scheduleDestroyActivities方法销毁Activity

scheduleDestroyActivities方法位于ActivityStack中,用于销毁Activity,代码如下:


final class ActivityStack{

……

final void scheduleDestroyActivities(ProcessRecord owner,

boolean oomAdj, String reason){

Message msg=mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG);

msg.obj=new ScheduleDestroyArgs(owner, oomAdj, reason);

mHandler.sendMessage(msg);

}


scheduleDestroyActivities向ActivityStack的消息循环中发送DESTROY_ACTIVITIES_MSG消息。ActivityStack消息处理器的代码如下:


final class ActivityStack{

final Handler mHandler=new Handler(){

public void handleMessage(Message msg){

switch(msg.what){

……

case DESTROY_ACTIVITIES_MSG:{

ScheduleDestroyArgs args=(ScheduleDestroyArgs)msg.obj;

synchronized(mService){

destroyActivitiesLocked(args.mOwner, args.mOomAdj, args.mReason);

}

……


handleMessage方法根据消息类型匹配到switch的DESTROY_ACTIVITIES_MSG分支,进而调用destroyActivitiesLocked方法销毁Activity。代码如下:


final void destroyActivitiesLocked(ProcessRecord owner,

boolean oomAdj, String reason){

boolean lastIsOpaque=false;//Opaque是不透明的意思,即覆盖全屏

boolean activityRemoved=false;

//遍历Activity历史栈中属于owner进程的Activity

for(int i=mHistory.size()-1;i>=0;i—){

ActivityRecord r=mHistory.get(i);

if(r.finishing){//正在finishing的不需要多此一举,直接跳过

continue;

}

if(r.fullscreen){//覆盖全屏

lastIsOpaque=true;

}

if(owner!=null&&r.app!=owner){//不匹配指定的进程

continue;

}

if(!lastIsOpaque){

continue;

}

/*同时满足条件:非当前正在显示的Activity,非当前正在暂停的

Activity,非可视Activity,非已停止,非正在销毁,非已销毁/

if(r.app!=null&&r!=mResumedActivity&&r!=mPausingActivity

&&r.haveState&&!r.visible&&r.stopped

&&r.state!=ActivityState.DESTROYING

&&r.state!=ActivityState.DESTROYED){

//销毁满足if条件的Activity

if(destroyActivityLocked(r, true, oomAdj, reason)){

activityRemoved=true;//Activity已销毁

}

}

}

if(activityRemoved){//销毁了Activity,需要显示栈顶Activity

resumeTopActivityLocked(null);//第11章分析过

}

}


destroyActivitiesLocked方法的主要工作是遍历Activity历史栈中属于owner进程的Activity,并调用destroyActivityLocked方法销毁满足条件的Activity。在销毁Activity后,还需要显示栈顶Activity。

接下来分析destroyActivityLocked,代码如下:


final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp,

boolean oomAdj, String reason){

boolean removedFromHistory=false;

//清理该Activity在ActivityStack中记录的信息

cleanUpActivityLocked(r, false, false);

final boolean hadApp=r.app!=null;//进程信息不为null

if(hadApp){

if(removeFromApp){//true

int idx=r.app.activities.indexOf(r);

if(idx>=0){

r.app.activities.remove(idx);//删除进程的Activity记录

}

……

if(r.app.activities.size()==0){

//更新LRU weight

mService.updateLruProcessLocked(r.app, oomAdj, false);

}

}

boolean skipDestroy=false;

try{//调度Activity生命周期的方法

r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,

r.configChangeFlags);

}catch(Exception e){

}

r.app=null;//清理Activity的进程信息

r.nowVisible=false;

//ActivityThread调度Activity生命周期的方法需要时间,在这里指定超时时间

if(r.finishing&&!skipDestroy){

r.state=ActivityState.DESTROYING;

Message msg=mHandler.obtainMessage(DESTROY_TIMEOUT_MSG);

msg.obj=r;

mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT);//10s后超时

}else{

r.state=ActivityState.DESTROYED;

}

}else{

……

}

……

return removedFromHistory;

}


destroyActivityLocked方法的主要工作是:首先通过ActivityThread.ApplicationThread回调Activity生命周期的方法以销毁Activity,然后向ActivityStack的消息循环中发送DESTROY_TIMEOUT_MSG消息,指定该消息在DESTROY_TIMEOUT(10s)后处理。如果ApplicationThread没有在10s内销毁Activity并通知ActivityStack移除DESTROY_TIMEOUT_MSG消息,说明销毁Activity的操作超时,进入ActivityStack的超时处理方法。r.app.thread返回的是ApplicationThreadProxy(BinderProxy),因此最终会调用ApplicationThread的scheduleDestroyActivity方法,该方法会向应用程序主线程的消息循环中发送DESTROY_ACTIVITY消息。这个处理流程与回调Activity其他生命周期方法的处理流程是相似的,这部分内容在第11章中已经详细分析过,在此不赘述。

应用程序ActivityThread的消息处理器会调用handleDestroyActivity方法销毁Activity,代码如下:


private void handleDestroyActivity(IBinder token, boolean finishing,

int configChanges, boolean getNonConfigInstance){

//通过Instrumentation回调Activity生命周期的方法

ActivityClientRecord r=performDestroyActivity(token, finishing,

configChanges, getNonConfigInstance);

if(r!=null){

……//通过Window Manager移除Activity相关窗口,不在本书分析

}

if(finishing){

try{

//通知ActivityManagerService(ActivityStack)操作完成

ActivityManagerNative.getDefault().activityDestroyed(token);

}catch(RemoteException ex){

//If the system process has died, it's game over for everyone.

}

}

}


handleDestroyActivity的主要工作是调用performDestroyActivity方法,进而通过Instrumentation回调Activity生命周期的方法销毁Activity,然后通过Window Manager清理Activity对应的窗口,最后通过Binder通信通知ActivityStack操作完成。Window Manager相关部分不在本书分析。ActivityStack得到通知后,首先会移除之前添加到自身消息队列中的DESTROY_TIMEOUT_MSG消息,然后移除Activity历史栈中该Activity的信息,这部分内容比较简单,也不做分析。

接下来分析performDestroyActivity方法,代码如下:


private ActivityClientRecord performDestroyActivity(IBinder token,

boolean finishing, int configChanges, boolean getNonConfigInstance){

ActivityClientRecord r=mActivities.get(token);

Class activityClass=null;

if(r!=null){

activityClass=r.activity.getClass();

r.activity.mConfigChangeFlags|=configChanges;

if(finishing){

r.activity.mFinished=true;

}

if(!r.paused){

try{

r.activity.mCalled=false;

//回调Activity的onPause方法

mInstrumentation.callActivityOnPause(r.activity);

……

}

……

r.paused=true;//修改状态值

}

if(!r.stopped){

try{

r.activity.performStop();//回调Activity的onPause方法

}

……

r.stopped=true;//修改状态值

}

……

try{

r.activity.mCalled=false;

//回调Activity的onDestroy方法

mInstrumentation.callActivityOnDestroy(r.activity);

……

}

……

}

mActivities.remove(token);//在应用程序中移除Activity的信息

StrictMode.decrementExpectedActivityCount(activityClass);

return r;

}


performDestroyActivity方法通过Instrumentation依次回调Activity生命周期的onPause、onStop和onDestroy方法。至此,Activity在应用程序进程中已经被销毁,剩下的工作便是通知ActivityStack销毁其在ActivityStack所在进程的信息。

至此,updateOomAdjLocked()方法的三个阶段就分析完了,接下来分析OOM adj的计算过程。