一、LowMemory Killer
Low Memory Killer在用户空间中指定了一组内存临界值,当其中的某个值与进程描述中的oom_adj值在同一范围时,该进程将被Kill掉。通常,在
/sys/module/lowmemorykiller/parameters/adj中指定oom_adj的最小值,在
/sys/module/lowmemorykiller/parameters/minfree中储存空闲页面的数量,所有的值都用一个逗号将其隔开且以升序排列。
1、Android单个进程内存分配策略
系统会给每一个进程分配内存,不同的Android设备有可能不一样,一般情况这个值在system/build.prop中可以查看,设定可以在system.prop中设定属性dalvik.vm.heapsize和dalvik.vm.heapgrowthlimit的值确定,Android L之后一般将值设定为:
dalvik.vm. heapgrowthlimit=128m dalvik.vm.heapsize= 256m |
dalvik.vm. heapgrowthlimit是一般情况下分配给APP的内存大小,dvmheap是可增长的,是给受控制的应用分配的,这些应用dvm heap size超过这个值会出现OOM的情况;在Android开发中,如果要使用dalvik.vm. heapsize ,需要在manifest中指定android:largeHeap为true,这样dvm heap最大可达dalvik.vm.heapsize,主要用在一些大型游戏。
2、lowmem_adj和lowmem_minfree
在kernel/drivers/staging/android/路径下的lowmemorykiller.c文件定义了lowmem_adj和lowmem_minfree两个数组,一个是adj 数组,描述process 所对应的oom_adj,另外一个是minfree数组,描述process 所对应的memory 的阈值。如下:
static int lowmem_adj[6]= { 0, 1, 6, 12, }; static int lowmem_adj_size = 4; static size_t lowmem_minfree[6]= { //其中的元素单位是Page,1page=4KB 3*512,// 6MB 2*1024,// 8MB 4*1024,// 16MB 16*1024,// 64MB }; static int lowmem_minfree_size =4; |
可以看出,当一个进程的空闲空间剩余为3*512个页面时,这时对应的lowmem_adj数组中的元素是0,所以表示oom_adj值为0或者更大的进程会被Kill掉;当一个进程的空闲空间下降到2´1024个页面时,oom_adj值为1或者更大的进程会被Kill掉,依此类推。其实更简明的理解就是满足以下条件的进程将被优先Kill掉:
可杀进程:优先级最低 + 内存占用最多
进程描述符中的signal_struct->oom_adj表示当内存短缺时进程被选择并Kill的优先级,取值范围是-17~15。如果是-17,则表示不会被选中,值越大越可能被选中。当某个进程被选中后,内核会发送SIGKILL信号将其Kill掉。
3、Low Memory Killer的具体实现
在了解了Low Memory Killer的原理之后,我们再来看如何实现这个驱动。Low Memory Killer驱动的实现位于kernel/drivers/misc/lowmemorykiller.c。该驱动的实现非常简单,其初始化与退出操作也是我们到目前为止见过的最简单的,代码如下:
static int __init lowmem_init(void) { register_shrinker(&lowmem_shrinker); return 0; } static void __exit lowmem_exit(void) { unregister_shrinker(&lowmem_shrinker); } module_init(lowmem_init); module_exit(lowmem_exit); |
在初始化函数lowmem_init中通过register_shrinker注册了一个shrinker为lowmem_shrinker,退出时又调用了函数lowmem_exit,通过unregister_shrinker来卸载被注册的lowmem_shrinker。其中lowmem_shrinker的定义如下:
static struct shrinker lowmem_shrinker= { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS *16 }; |
lowmem_shrink是这个驱动的核心实现,当内存不足时就会调用lowmem_shrink方法来Kill掉某些进程。下面来分析其具体实现,实现代码如下:
static int lowmem_shrink(struct shrinker*s, struct shrink_control*sc) { struct task_struct *tsk; struct task_struct *selected= NULL; int rem =0; int tasksize; int i; short min_score_adj = OOM_SCORE_ADJ_MAX +1; int minfree =0; int selected_tasksize = 0; short selected_oom_score_adj; int array_size = ARRAY_SIZE(lowmem_adj); int other_free = global_page_state(NR_FREE_PAGES)- totalreserve_pages; int other_file = global_page_state(NR_FILE_PAGES)- global_page_state(NR_SHMEM);
..............................
lowmem_print(4,"lowmem_shrink %lu, %x, return %d\n", sc->nr_to_scan, sc->gfp_mask, rem); rcu_read_unlock(); spin_unlock(&lowmem_shrink_lock); return rem; } |
首先确定我们所定义的lowmem_adj和lowmem_minfree数组的大小(元素个数)是否一致,如果不一致则以最小的为基准,因为我们需要通过比较lowmem_minfree中的空闲储存空间的值,以确定最小min_adj值(当满足其条件时,通过其数组索引来寻找lowmem_adj中对应元素的值):
if (lowmem_adj_size < array_size) array_size = lowmem_adj_size; if (lowmem_minfree_size < array_size) array_size = lowmem_minfree_size; for (i = 0; i < array_size; i++){ minfree = lowmem_minfree[i]; if (other_free < minfree&& other_file < minfree){ min_score_adj = lowmem_adj[i]; break; } } |
其次检测min_adj的值是否是初始值“OOM_ADJUST_MAX + 1”,如果是,则表示没有满足条件的min_adj值,否则进入下一步;然后使用循环 for_each_process(p)对每一个进程块进行判断,通过min_adj来寻找满足条件的具体进程(主要包括对oomkilladj和task_struct进行判断):
if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX+ 1){ lowmem_print(5,"lowmem_shrink %lu, %x, return %d\n", sc->nr_to_scan, sc->gfp_mask, rem); spin_unlock(&lowmem_shrink_lock); return rem; } |
最后,对找到的进程进行NULL判断,通过“send_sig(SIGKILL, selected, 0);”发送一条SIGKILL信号到内核,Kill掉被选中的“selected”进程。
可以看出,其中多处用到了global_page_state函数。有很多人找不到这个函数,其实它被定义在了kernel/include/linux/vmstat.h中,其参数使用zone_stat_item枚举,被定义在kernel/include/linux/mmzone.h中。
二、上层实现
Android的每一个应用都是运行在一个独立的DVM中,他们之间互不影响;应用退出之后,并没有立马杀死进程,进程依然停留在内存中,这么做的目的是为了提高下次启动时的速度。而在Android中管理进程的模块是AMS,主要有LRU weight、OOM adj、Low MemoryKiller共同来完成进程的管理。
在ActivityManagerService.java初始化了对象ProcessList.java。对进程的oom_adj的处理都是在ProcessList中进行的。
1、oom_adj和oom_score_adj
AMS 启动后,将根据屏幕分辨率以及内存大小调整默认的LMK 的阈值,对应的具体的代码在:
frameworks/base/services/java/com/android/server/am/ProcessList.java
可以修正方法updateOomLevel来调整LMK 的oom_adj和oom_score_adj这两个参数,具体代码如下:
ProcessList() { MemInfoReader minfo =new MemInfoReader(); minfo.readMemInfo(); mTotalMemMb = minfo.getTotalSize()/(1024*1024); updateOomLevels(0,0, false); }
private void updateOomLevels(int displayWidth,int displayHeight,boolean write){ // Scale buckets from avail memory: at 300MB we use the lowest values to // 700MB or more for the top values. float scaleMem = ((float)(mTotalMemMb-300))/(700-300);
// Scale buckets from screen size. int minSize =320*480; // 153600 int maxSize =1280*800;// 1024000 230400 870400 .264 float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize); //Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight);
StringBuilder adjString =new StringBuilder(); StringBuilder memString =new StringBuilder();
float scale = scaleMem> scaleDisp ? scaleMem: scaleDisp; if (scale < 0) scale =0; else if (scale >1) scale= 1; for (int i=0; i<mOomAdj.length; i++) { long low = mOomMinFreeLow[i]; long high = mOomMinFreeHigh[i]; mOomMinFree[i]= (long)(low+ ((high-low)*scale));
if (i > 0) { adjString.append(','); memString.append(','); } adjString.append(mOomAdj[i]); memString.append((mOomMinFree[i]*1024)/PAGE_SIZE); }
//Slog.i("XXXXXXX", "******************************* MINFREE: " + memString); if (write) { writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString()); writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString()); } // GB: 2048,3072,4096,6144,7168,8192 // HC: 8192,10240,12288,14336,16384,20480 } |
2、进程的oom_adj值来源
1) init.rc中,init进程的pid为1,omm_adj被配置为-16,永远不会被杀死。
on early-init # Set init and its forked children's oom_adj. write /proc/1/oom_adj-16
start ueventd |
2) 通过Process.setOomAdj(int pid, int amt)进行设置,其中在ActivityManagerService中applyOomAdjLocked()函数中就有调用。
public static final native boolean setOomAdj(int pid,int amt); |
android_util_Process.cpp
jboolean android_os_Process_setOomAdj(JNIEnv* env, jobject clazz, jint pid, jint adj) { #ifdef HAVE_OOM_ADJ char text[64]; sprintf(text,"/proc/%d/oom_adj", pid); int fd = open(text, O_WRONLY); if (fd >= 0) { sprintf(text,"%d", adj); write(fd, text, strlen(text)); close(fd); } return true; #endif return false; } |
3、ActivityManageService.java中的OOM
在AMS中主要是在进程数达到阈值ProcessList.CACHED_APP_MAX_ADJ时对kill进程的选择过程,进程的oom_adj也会做相应的调整,下面是函数updateOomAdjLocked:
final int emptyProcessLimit; final int cachedProcessLimit; if (mProcessLimit <=0) { emptyProcessLimit = cachedProcessLimit= 0; } else if (mProcessLimit== 1){ emptyProcessLimit =1; cachedProcessLimit =0; } else { emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit); cachedProcessLimit = mProcessLimit- emptyProcessLimit; }
// Let's determine how many processes we have running vs. // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. int numSlots =(ProcessList.CACHED_APP_MAX_ADJ - ProcessList.CACHED_APP_MIN_ADJ+ 1)/ 2; int numEmptyProcs = N - mNumNonCachedProcs- mNumCachedHiddenProcs; if (numEmptyProcs > cachedProcessLimit){ // If there are more empty processes than our limit on cached // processes, then use the cached process limit for the factor. // This ensures that the really old empty processes get pushed // down to the bottom, so if we are running low on memory we will // have a better chance at keeping around more cached processes // instead of a gazillion empty processes. numEmptyProcs = cachedProcessLimit; } |
在上面,emptyProcessLimit的值是ProcessList.MAX_CACHED_APPS的2/3,这里只是对一些空进程的操作,N = mLruProcesses.size()是最少使用进程的数目;来看下在AMS中的updateLruProcessLocked方法:
final void updateLruProcessLocked(ProcessRecord app,boolean activityChange, ProcessRecord client){ final boolean hasActivity = app.activities.size()> 0 || app.hasClientActivities; final boolean hasService = false; // not impl yet. app.services.size() > 0; if (!activityChange && hasActivity){ // The process has activties, so we are only going to allow activity-based // adjustments move it. It should be kept in the front of the list with other // processes that have activities, and we don't want those to change their // order except due to activity operations. return; } .............................................. //分为三种情况:activity,service,其他 int nextIndex; if (hasActivity) { final int N = mLruProcesses.size(); if (app.activities.size()== 0 && mLruProcessActivityStart <(N-1)){ // Process doesn't have activities, but has clients with // activities... move it up, but one below the top (the top // should always have a real activity). if (DEBUG_LRU) Slog.d(TAG,"Adding to second-top of LRU activity list: " + app); mLruProcesses.add(N-1, app); // To keep it from spamming the LRU list (by making a bunch of clients), // we will push down any other entries owned by the app. final int uid = app.info.uid; for (int i=N-2; i>mLruProcessActivityStart; i--) { ProcessRecord subProc = mLruProcesses.get(i); if (subProc.info.uid== uid){ // We want to push this one down the list. If the process after // it is for the same uid, however, don't do so, because we don't // want them internally to be re-ordered. if(mLruProcesses.get(i-1).info.uid!= uid){ if(DEBUG_LRU) Slog.d(TAG,"Pushing uid " + uid+ " swapping at "+ i +": " + mLruProcesses.get(i)+ " : " + mLruProcesses.get(i-1)); ProcessRecord tmp = mLruProcesses.get(i); mLruProcesses.set(i, mLruProcesses.get(i-1)); mLruProcesses.set(i-1, tmp); i--; } } else { // A gap, we can stop here. break; } } } else { // Process has activities, put it at the very tipsy-top. if (DEBUG_LRU) Slog.d(TAG,"Adding to top of LRU activity list: " + app); mLruProcesses.add(app); } nextIndex = mLruProcessServiceStart; } else if (hasService){ // Process has services, put it at the top of the service list. if (DEBUG_LRU) Slog.d(TAG,"Adding to top of LRU service list: " + app); mLruProcesses.add(mLruProcessActivityStart, app); nextIndex = mLruProcessServiceStart; mLruProcessActivityStart++; } else { // Process not otherwise of interest, it goes to the top of the non-service area. int index = mLruProcessServiceStart; if (client != null) { // If there is a client, don't allow the process to be moved up higher // in the list than that client. int clientIndex= mLruProcesses.lastIndexOf(client); if (DEBUG_LRU && clientIndex< 0) Slog.d(TAG,"Unknown client " + client +" when updating " + app); if (clientIndex <= lrui){ // Don't allow the client index restriction to push it down farther in the // list than it already is. clientIndex = lrui; } if (clientIndex >= 0 && index > clientIndex){ index = clientIndex; } } if (DEBUG_LRU) Slog.d(TAG,"Adding at " + index+ " of LRU list: "+ app); mLruProcesses.add(index, app); //确定APP在mLruProcesses的位置 nextIndex = index-1; mLruProcessActivityStart++; mLruProcessServiceStart++; } //如果当前应用使用了ContentProvider或是Service,重新计算 // If the app is currently using a content provider or service, // bump those processes as well. for (int j=app.connections.size()-1; j>=0; j--){ ConnectionRecord cr = app.connections.valueAt(j); if (cr.binding != null && !cr.serviceDead&& cr.binding.service!= null && cr.binding.service.app!= null && cr.binding.service.app.lruSeq!= mLruSeq && !cr.binding.service.app.persistent){ nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--){ ContentProviderRecord cpr = app.conProviders.get(j).provider; if (cpr.proc != null && cpr.proc.lruSeq!= mLruSeq &&!cpr.proc.persistent){ nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, "provider reference", cpr, app); } } } |
LRUweight LRU(最近最少使用)weight主要用来衡量LRU的权重,在android进程启动之后,会以ProcessRecord类型的方式创建一个实例,保存到AMS的mLruProcesses变量中,mLurProcesses会以LRU的顺序来存储进程信息。当有一下情况时会更新mLruProcesses: 1.应用程序异常退出;2.调用AMS杀死进程;3.启动和调度四大组件。
updateLruProcessLocked有两个作用:
Ø 计算进程的LRU序列号和LRU weight;
Ø 在1的基础上,确定在mLruProcesses中的位置;
再来看下updateOomAdjLocked函数,这个函数经过了多次重载:
private final boolean updateOomAdjLocked(ProcessRecord app,int cachedAdj, ProcessRecord TOP_APP,boolean doingAll,boolean reportingProcessState,long now){ if (app.thread == null){ return false; }
final boolean wasKeeping = app.keeping; //computeOomAdjLocked会重新计算进程的oom_adj的值 computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now);
return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, reportingProcessState, now); } final void updateOomAdjLocked() { ............. for (int i=N-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); if (!app.killedByAm&& app.thread!= null){ app.procStateChanged= false; final boolean wasKeeping = app.keeping; //计算oom_adj computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
// If we haven't yet assigned the final cached adj // to the process, do that now. if (app.curAdj >= ProcessList.UNKNOWN_ADJ){ switch(app.curProcState){ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: 。。。。。。 default: 。。。。。。 break; } } //调用applyOomAdjLocked,继而调用setOomAdj和setProcessGroup applyOomAdjLocked(app, wasKeeping, TOP_APP,true, false, now);
// Count the number of process types. switch (app.curProcState){ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: mNumCachedHiddenProcs++; numCached++; if(numCached > cachedProcessLimit){ //kill掉不需要的进程 killUnneededProcessLocked(app,"cached #" + numCached); } break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: if(numEmpty > ProcessList.TRIM_EMPTY_APPS && app.lastActivityTime< oldTime){ //kill掉不需要的进程 killUnneededProcessLocked(app,"empty for " +((oldTime + ProcessList.MAX_EMPTY_TIME- app.lastActivityTime) /1000) + "s"); }else { numEmpty++; if(numEmpty > emptyProcessLimit){ //kill掉不需要的进程 killUnneededProcessLocked(app,"empty #" + numEmpty); } } break; default: mNumNonCachedProcs++; break; }
if (app.isolated && app.services.size()<= 0){ // If this is an isolated process, and there are no // services running in it, then the process is no longer // needed. We agressively kill these because we can by // definition not re-use the same process again, and it is // good to avoid having whatever code was running in them // left sitting around after no longer needed. //kill掉不需要的进程 killUnneededProcessLocked(app,"isolated not needed"); }
if (app.curProcState>= ActivityManager.PROCESS_STATE_HOME &&!app.killedByAm){ numTrimming++; } } }
mNumServiceProcs = mNewNumServiceProcs; } |
其实在updateOomAdjLocked主要做了三个操作:
Ø 调用computeOomAdjLocked方法计算进程的oom_adj的值;
Ø 调用setOomAdj来修改进程的oom adj的值;
Ø 调用killUnneededProcessLocked来kill掉不适用的进程;
updateOomAdjLocked在后面还做了一些对内存的处理优化操作,这里就不介绍了;这里有一个疑问,底层和上层都有kill进程的过程,其中有什么区别呢,我的想法是这样的,底层的kill主要发生在进程出现Low Memory的时候才去做,而上层在AMS中是系统在检测到内存不足时清理缓存的一个重要过程。
三、实用举例
避免后台音乐或是其他应用被lowmemory清理掉:
解决方案:
1、在ActivityManagerService.java中添加如下两个变量:
static final String[] mThirdPartyAppWhiteList= {"android.process.media"}; static final int [] mThirdPartyAppAdj= {7}; |
2、在updateOomAdjLocked做这样的修改:
private final boolean updateOomAdjLocked(ProcessRecord app,int cachedAdj, ProcessRecord TOP_APP,boolean doingAll,boolean reportingProcessState,long now){ if (app.thread == null){ return false; }
final boolean wasKeeping = app.keeping;
computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now); //Add by Jack-Yu boolean isThirdPartyAppWhiteProcess= false; int mThirdPartyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; if (mThirdPartyAppWhiteList.length!= mThirdPartyAppAdj.length) { throw new Exception("mThirdPartyAppWhiteList is not match mThirdPartyAppAdj"); } for(int num= 0; num<= mThirdPartyAppWhiteList.length-1;num++) { if(mThirdPartyAppWhiteList[num].equals(app.processName)&& app.curAdj> mThirdPartyAppAdj[num]) { isThirdPartyAppWhiteProcess =true; mThirdPartyAdj = mThirdPartyAppAdj[num]; } } if(isThirdPartyAppWhiteProcess){ if (Process.setOomAdj(app.pid, mThirdPartyAdj)){ if (DEBUG_SWITCH || DEBUG_OOM_ADJ){ Slog.v(TAG,"Set " + app.pid+ " "+ app.processName+ " adj " + mThirdPartyAdj +": " + app.adjType); } app.setAdj= mThirdPartyAdj; } else { Slog.w(TAG,"Failed setting oom adj of " + app+ " to "+ mThirdPartyAdj); return false }
return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, reportingProcessState, now); } |
上面代码没有经过编译,但大体的代码逻辑还是写清楚了,就设置一个白名单,将APP的oom_adj值设置为7,是PREVIOUS_APP_ADJ,表征前一个进程,这样oom_adj将达到非当前不可见APP的最小值,最后在返回的时候返回false即可。