内存优化学习日志(一)

内存优化学习日志(一)


2017/8/3 星期四 10:58:59

Android内存分配回收机制

1.1 Android APP 运行环境

Android 是一款基于Linux内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊要求,谷歌对其做了特别的设计与优化,使得其进程调度与资源管理与其他平台的Linux有明显的区别。主要包含下面几个层次:

1.1.1 Application Framework

Application Framework将整个操作系统分隔成两个部分。对应用开发者而言,所有APP都是运行在Application Framework之上,而不用关心系统底层的情况。Application Frmaework层为应用开发者提供了丰富的应用编程接口,如Activity Manager,Content Provider,Notification Manager,以及各种窗口Widget资源等。在Application Framework层,Activity是一个APP最基本的组成部分。一般每个Activity对应于屏幕上的一个视图,一个APP可以有一个或者多个Activity。应用程序被打包成.apk文件格式,由Dalvik VM 解释执行。

1.1.2 Dalvik VM

Dalvik 虚拟机采用寄存器架构,而不是JVM的栈结构。Java程序编译后的.class文件并不能在Delvik中解释执行。因此Google提供了一个dx工具,用于将.class文件转换成Dalivk能够识别的.dex格式。

1.1.3 Linux kernel

由上所述,所有的APP都是由于Java代码编写并在DalvikVM中的得到解释执行。在Android操作系统中,每个Dalvik VM的每个Instance都对应于Linux内核中的每一个进程。可以使用adb shell 工具查看系统中的当前进程。(adb shell ps)
如下图所示,Android启动后内核中的进程列表。
图1 Android 中的进程列表(部分)

图1中,UID 标识为 app_xx 的每一项都是一个 app 所占用的进程,可见 Android 设计使得每个应用程序由一个独立的 Dalvik 实例解释执行,而每个 Linux 内核进程加载一个 Dalvik 实例,通过这种方式提供 app 的运行环境。如此,每个 APP 的资源被完全屏蔽,互不干扰。虽然同时引入了进程间通信的困难,但也带来了更强的安全性。

1.2 Android 内存回收原则

下面将从Application Framework和Linux kernel两个层次分析Android操作系统的资源管理机制。


Android之所以采用特殊的资源管理机制,原因在于其设计之初就是面向移动终端,所有可用的内存仅限于系统RAM,必须针对这种限制设计相应的优化方案。当Android应用程序退出时,并不清理其所占用的内存,Linux内核进程也相应的继续存在,所谓“退出但不关闭”。从而使用户调用程序时能够在第一时间得到响应。当系统内存不足时,系统将激活内存回收过程。为了不因内存回收影响用户体验(如杀死当前的活动进程),Android基于进程中运行的组件及其状态规定了默认的五个回收优先级:

IMPORTANCE\_FOREGROUND:

IMPORTANCE\_VISIBLE:

IMPORTANCE\_SERVICE:

IMPORTANCE\_BACKGROUND:

IMPORTANCE\_EMPTY:

这几种优先级的回收顺序是Empty process、 Background process、Service process、Visible process、Foreground process。关于划分原则参见 [http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html](http://developer.android.com/guide/topics/fundamentals/processes-and-threads.html)文件中

ActivityManagerService集中管理所有进程的内存资源分配。所有进程需要申请或释放内存之前必须调用ActivityManagerService对象,获得其“许可”之后才能进行下一步操作,或者ActivityManagerService将直接“代劳”。类ActivityManagerService中涉及到内存回收的几个重要的成员方法如下:trimApplication(),updateOomAdjLocked(),activityIdleInternal()。这几个成员方法主要负责Android默认的内存回收机制,若Linux内核中的内存回收机制没有被禁用,则跳过默认回收。

###1.3 默认回收过程 Android 操作系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认回收机制进行研究,Liunx内核层次的内存回收机制将在下一张介绍。本章所有的代码可参见ActivityManagerService.java。 ####1.3.1 回收动作入口:activityIdleInternal() Android 系统中内存回收的触发点大致可以分为三种情况。第一种,用户程序调用StartActivity(),使当前活动的Activity被覆盖;第二种,用户按Back键,退出当前应用程序;第三种,启动一个新的应用程序。这些能够触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当ActivityManagerService接收到异步消息IDLE\_TIMEOUT\_MSG或者IDLE\_NOW\_MSG时,activityIdleInternal()将会被调用。代码如下:

清单1.IDLE_NOW_MSG的处理方式


  case IDlE_NOW_MSG:{ 
IBinder token=(Ibinder)msg.obj;
activityIdle(token,null);
}
break;

清单2.IDLE_TIMEOUT_MSG的处理方式

case IDLE_TIMEOUT_MSG: { 
if (mDidDexOpt) { 
       mDidDexOpt = false; 
       Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); 
       nmsg.obj = msg.obj; 
       mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); 
       return; 
   } 
   IBinder token = (IBinder)msg.obj; 
   Slog.w(TAG, "Activity idle timeout for " + token); 
   activityIdleInternal(token, true, null); 
} 
break;

IDIE_NOW_MSG由Activity的切换以及Activity焦点的改变等事件引发,IDLE_TIMEOUT_MSG在Activity启动超时的情况下引发,一般这个超时时间设为10S,如果10S内一个Activity依然没有启动成功,那么将发送异步消息IDLE_TIMEOUT_MSG进行资源回收。activityIdleInternal()的主要任务是改变系统中Activity的状态信息,并将其添加到不同状态列表中。主要工作如下:

首先,调用scheduleAppGcsLocked()方法通知所有进行中的任务进行垃圾回收。scheduleAppGcsLocked()将进行调度JVM的garbage collect,回收一部分内存空间,这里仅仅是通知每个进程自行进程垃圾检查并调度回收时间,而非同步回收。然后,取出mStoppingActivities和mFinishigActivitieslies列表中的所有内容,暂存在临时变量中。这两个列表分别存储了当前状态为stop和finish的activity对象。对于stop列表,如果其中的activity的finish状态为ture,判断是不是要立即停止,如果要立即停止则调用destroyActivityLocked()通知目标进程调用OnDestroy()方法,否则,先调用resumeTopActivity()运行下一个Activity。如果finish状态为false,则调用stopActivityLocked()通知客户进程停止该Activity,这种情况一般发生在调用startActivity()后。对于finish列表,直接调用的destroyActivityLocked()通知客户进程销毁目标Activity。

这里的destroyActivityLocked等函数并没有真正意义上改变内存使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的trimApplications()函数中。

1.3.2 回收过程函数: trimApplications()

trimApplications()函数的结构如下:

清单3.trimApplications函数

private final void trimApplications(){
    synchronized(this){
        //First remove any unused application process whose package
        //has been removed
        for (i=mRemovedProcesses,size()-1;i>=0;i--){
            (1) //kill process;
        }
        if (!updateOomAdjLocked){
            (2) //do something default
        }
        //Finally,if there are too many activities now running ,tyr to
        //finish as many as we can to get back to the limit
        (3) do something
    }
}

清单3 中的三个标序号的位置分别负责如下工作:

(1)当程序执行到trimApplications()之后,首先检查mRemovedProcesses列表中的进程。mRemovedProcessesles列表中主要包含了crash的进程、5秒内没有响应并被用户选在强制关闭的进程以及应用开发这边调用killBackgroundProcess想要杀死的进程。调用Process.killProcess将所有此类进程全部杀死。

(2)调用updateOomAdjLocked()函数,若成功返回,说明Linux内核支持setOomAdj()接口,updateOomAdjLocked将修改adj的值并通知Linux内核,内核根据adj值以及内存使用情况动态管理进程资源(lowmemorykiller和oom_killer)。若updateOomAdjLocked()返回为假,则表示当前系统不支持setOomAdj()接口,将在本地进行默认的资源回收。

(3)最后,如果当前依然运行了过多的Activity,对多余的Activity进行回收。trimApplications()的大多数的代码都在处理Oom_killer不存在情况下的默认资源回收,下面对其默认回收过程(即代码清单(2)的位置)进行进一步分析。其回收过程可大致描述如下。

步骤一,获取当前所有运行的进程mLruProcesses,mLruProcesses中的排序规则是按最近使用时间。对mLruProcesses中不能被关闭的的进程计数,这些不能被关闭的进程包括运行service的进程,运行broadcastreceiver的进程,见如下代码。

清单4.计数不能被关闭的进程

if (app.persistent || app.services.size() !=0
            || app.curReceiver != null
            || app.presistentActivities > 0) {
    // Don't count processes holding services against our
    // maximum process count.
            numServiceProcs++;
        }

步骤二,设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs (即默认最大进程数与运行Service的进程数之和),如果当前进程的数量mRemovedProcesses.size()大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。清理过程见清单5 (部分代码省略)。从清单5 的代码中可以看出,进程被杀死的条件是:

  • 必须是非 presistent进程,即非系统进程;
  • 必须是空进程,即进程中没有任何activity存在。如果杀死存在Activity的进程,有可能关闭用户正在使用的程序,或者是应用程序恢复的时延变大,从而影响用户体验;
  • 必须无broadcast receiver。运行在broadcast receiver一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;
    *进程中service的数量必须为0 。存在service的进程很有可能为一个或者多个程序提供某种服务,如GPS地位服务。杀死此类进程将使其他进程无法正常服务。

清单5.清理过程

if (!app.presistent && app.activities.size==0 
            && app.curReceiver == null && app.services.size() == 0){
        if (app.pid > 0 && app.pid !=MY_PID){
            Porcess.killProcess(app.pid);
            }else{
            try{
                app.thread.scheduleExit();
            }catch(Exception e){
                //Ingore exceptions.
            }
        }
        // todo: For now we assume the application is not buggy
        // or evill, and will quit as a result of our request.
        //Eventually we need to drive this off of the death
        //notification, and kill the process if it takes too long.
        cleanUpApplicationRecordLocked(app,false,i);
        i--;
}

步骤三,再次检查当前运行的进程,如果 mRemoveProcesses.size()仍然大于 curMaxProcs,则放宽条件再次回收。判断条件见代码清单6 (部分代码省略)。下面代码中,布尔变量 canQuit 的值为真时,那么这个进程被回收。canQuit的取值分为两个步骤,首先是根据进程的属性赋值。1.必须是非 persistent 进程,即非系统进程;2.必须无 broadcast receiver;3.进程中service的数量必须为0;4.persistent类型的activity数量为0。与步骤二唯一的不同在第4条,这里不要求进程是空进程,只要进程中没有persistent类型的Activity就可以(Activity是否是persistent类型在开发阶段指定)。这些条件都满足时,再检查进程中每个Activity都还必须满足三个条件: Activity的状态已经保存,当前处在不可见状态并且Activity已经Stop。这时杀掉进程只会降低下次调用程序时的加载速度,下次启动时将恢复到关闭之前的状态,并不会在用户体验上造成致命的影响,所以,canQuit置为真。这种情况下与步骤二的回收方式也有不同,由于进程中Activity的数量不是0,下一步需要对每个Activity执行destroyActivityLocked()销毁,最后才杀死进程。

清单6.执行destroyActivityLocked()销毁

boolean canQuit = !app.persistent && app.curReceiver == null
        && app.services.size() ==0
        && app.persistentActivities.size() == 0;
int  NUMA = app.activities.size();
for (j=0; j 

1.4 Linux 内核中的内存回收

1.4.1 lowmemorykiller

上面提到,trimApplications()函数中会执行一个叫做 updateOomAdjLocked() 的函数,如果返回false,则执行默认回收,若返回true则不执行默认内存回收。
updateOomAdjLocked将针对每一个进程更新一个名为adj的变量,并将其告知Linux内核,内核维护一个包含adj的数据结构(即进程表),并通过 lowmemorykiller检查系统内存的使用情况,在内存不足的情况下杀死一些进程并释放内存。下面将对这种Android Framework 与 Linux内核想配合的内存回收机制进行研究。

由于Android 操作系统中的所有应用程序都运行在独立的Dalvik虚拟机环境中,Linux内核无法获知每个进程的运行状态,也就无法为每个进程维护一个合适的adj值,因此,Android Application FrameWork中必须提供一套机制以动态的更新每个进程的adj。这就是 updateOomAdjLocked()。

updateOomAdjLocked()位于 ActivityManagerService中,起主要作用是为进程选择一个合适的adj值,并通知Linux 内核更行这个值。updateOomAdjLocked 首先调用computeOom,AdjLocked()初步计算adj 的值,然后回到updateOomAdjLocked()对其值进行进一步修正。估算流程可参见代码。

关于adj,其定义在 task_sturct ->signal_struct->adj,文件/kerne/include/linux/sched.h中。实质为进程数据结构中的一个变量,用来表示发生 Out of Memory 时杀死进程的优先级顺序。lowmemorykiller 利用这个变量对进程的重要程度进行判断,并在内存不足时释放部分空间,其实现在文件 /kernel/drivers/staging/android/lowmemorykiller.c 中。lowmemorykiller定义了两个数组: lowmem_adj 和lowmem_minfree。其中lowmen_adj定义了一系列adj键值,而low_memfree的每个元素代表一个内存阈值。如下代码中四个阈值分别为6MB,8MB,16MB和64MB,分别代表内存小于64MB时,adj大于或等于12的那些进程将被杀死并回收,内存小于16MB时,adj大于等于6的那些进程将被杀死并回收,内存小于8MB时,adj大于等于1的那些进程将被杀死并回收,内存小于6MB时,adj 大于等于0的所有进程将被杀死并回收。内核中的每个进程都持有一个adj,取值范围 -17到15,值越小代表进程的重要性越高,回收优先级越低,其中 -17代表禁用自动回收。Android系统中,只有 0-15 被使用。

清单8.每个进程都持有一个adj

static int lowmem_adj[6] = {
    0,
    1,
    6,
    12,
};
static int lowmem_adj_size = 4;
static Size_t lowmem_minfree[6] ={
    3 * 512,        /* 6MB */
    2 * 1024,       /* 8MB */
    4 * 1024,       /* 16MB */
    16 * 1024,      /* 64MB */
 }
static int lowmen_minfree_size = 4;
lowmemorykiller 注册一个lowmem_shrinker,lowmem_shrinker 利用了标准Linux 内核中的CacheShrinker来实现,当空闲内存页面不足时,内核线程kswapd将用已注册的lowmem_shrinke来回收内存页面。 清单9. 用已经注册的lowmen_shrinker来回收内存页面
static struct shrinker lowmem_shrinker ={
    .shrink = lowmem_shrink,
    .seeks = DEFAULT_SEEK * 16
};
static int __init lowmem_init(void)
{
    task_free_register(&task_nb);
    register_shrinker(&lowmen_shrinker);
    return 0;
}

lowmem_shrink 的代码在函数lowmem_shrink中,下面给出该函数的主要结构。lowmem_shrink 根据上述规则遍历所有进程,选出需要结束的进程,通过发送一个无法忽略的信号SIGKILL强制结束这些进程。

清单10.强制结束进程

static int lowmem_shrink(struct shrinker *s, int nr_to_scan,gfp_t gfp_mask)
{
    for_each_process(p){
        //Select process to be forced
    }
    if (selected){
        force_sig(SIGKILL,selected);
        rem -= selected_tasksize;
    }else
    rem = -1;
    return rem;
}
1.4.2 Oom_killer

如果上述各种方法都无法释放出足够的内存空间,那么当为新的进程分配应用程序时将发生 Out of Memory 异常,OOM_killer 将尽最后的努力杀掉一些进程来释放空间。Android中的OOM_killer继承自标准Linux 2.6 内核,用于分配内存时 Out of Memory 的处理。Android并没有对其实现方式进行修改。其位置在linux/mm/oom_kill.c。oom_killer遍历进程,并计算所有进程的badness 值,选择 badness 最大的那个进程将其杀掉。函数badness的声明如下:

unsignd long badness(struct task_struct *p,unsigned long uptime)函数select_bad_process 返回将要杀掉的那个进程。

清单11. 返回将要杀掉的进程

static struct task_struct *select_bad_process(unsigned long *ppoints,
struct mem_cgroup *mem)
{
    for_each_process(p) {
        points = badness(p,uptime.tv_sec);
        if (point > *ppoints || !chosen){
            chosen = p;
            *ppoints = points;
        }
    }
    return chosen;
}

最后,和lowmemorykiller一样,通过发送SIGKILL结束选中的进程。由于oom_killer与标准的Linux内核并无不同,这里不再详细研究。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值