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的计算过程。