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

一、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即可。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值