关于OOM和内存回收的一点浅显的认识

来源:互联网 发布:rbac权限管理系统源码 编辑:程序博客网 时间:2024/04/29 00:12

一、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]={    //其中的元素单位是Page1page=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)进行设置,其中在ActivityManagerServiceapplyOomAdjLocked()函数中就有调用。

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_APPS2/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;

        }

        ..............................................

        //分为三种情况:activityservice,其他

        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); //确定APPmLruProcesses的位置

            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类型的方式创建一个实例,保存到AMSmLruProcesses变量中,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,继而调用setOomAdjsetProcessGroup

                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的值;

Ø  调用killUnneededProcessLockedkill掉不适用的进程;

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);

    }

   上面代码没有经过编译,但大体的代码逻辑还是写清楚了,就设置一个白名单,将APPoom_adj值设置为7,是PREVIOUS_APP_ADJ,表征前一个进程,这样oom_adj将达到非当前不可见APP的最小值,最后在返回的时候返回false即可。

0 0
原创粉丝点击