Android操作系统之内存回收策略
Android是一款基于Linux内核,面向移动终端的操作系统。为适应其作为移动平台操作系统的特殊需要,谷歌对其做了特别的设计与优化,使应用程序关闭但不退出,并由操作系统进行进程的回收管理。本文在ApplicationFramework与Linux内核两个层次上,以进程为粒度,对Android操作系统的进程资源回收机制进行了剖析。读者可以从本文获得对Android应用程序的生存周期的进一步理解,从而更加合理、高效地构建应用程序。
Android操作系统中的内存回收可分为两个层次:
1、默认内存回收、即ApplicationFramework层的默认回收。
2、内核级内存回收。
Linux内核中的内存回收lowmemorykiller、OOM_killer。
默认内存回收:(代码可查阅ActivityManagerService.java类)回收动作入口activityIdleInternal()。
Android系统中内存回收的触发点大致可分为三种情况。
第一,用户程序调用StartActivity(),使当前活动的Activity被覆盖;
第二,用户按back键,退出当前应用程序;第三,启动一个新的应用程序。
这些能够触发内存回收的事件最终调用的函数接口就是activityIdleInternal()。当ActivityManagerService接收到异步消息IDLE_TIMEOUT_MSG或者IDLE_NOW_MSG时,activityIdleInternal()将会被调用。IDLE_NOW_MSG由Activity的切换以及Activiy焦点的改变等事件引发,IDLE_TIMEOUT_MSG在Activity启动超时的情况下引发,一般这个超时时间设为10s,如果10s之内一个Activity依然没有成功启动,那么将发送异步消息IDLE_TIMEOUT_MSG进行资源回收。activityIdleInternal()的主要任务是改变系统中Activity的状态信息,并将其添加到不同状态列表中。它的主要工如下:
首先,调用scheduleAppGcsLocked()方法通知所有进行中的任务进行垃圾回收。scheduleAppGcsLocked()将进行调度JVM的garbagecollect,回收一部分内存空间,这里仅仅是通知每个进程自行进程垃圾检查并调度回收时间,而非同步回收。
然后,取出mStoppingActivities和mFinishigActivities列表中的所有内容,暂存在临时变量中。这两个列表分别存储了当前状态为stop和finishi的activity对象。对于stop列表,如果其中的activity的finish状态为true,判断是不是要立即停止,如果要立即停止则调用destroyActivityLocked()通知目标进程调用onDestroy()方法,否则,先调用resumeTopActivity()运行下一个Activity。如果finish状态为false,则调用stopActivityLocked()通知客户进程停止该Activity,这种情况一般发生在调用startActivity()后。对于finish列表,直接调用destroyActivityLocked()通知客户进程销毁目标Activity。这里的destroyActivityLocked等函数并没有真正意义上改变内存的使用,只是将其状态改变为“允许回收”,真正的回收在下面即将调用的trimApplications()函数中。
privatefinalvoidtrimApplications(){ synchronized(this){ //Firstremoveanyunusedapplicationprocesseswhosepackage //hasbeenremoved. for(i=mRemovedProcesses.size()-1;i>=0;i--){ (1)//killprocess; } if(!updateOomAdjLocked()){ (2)//dosomethingdefault } //Finally,iftherearetoomanyactivitiesnowrunning,tryto //finishasmanyaswecantogetbackdowntothelimit. (3)dosomething } }
(1)当程序执行到trimApplications()之后,首先检查mRemovedProcesses列表中的进程。mRemovedProcesses列表中主要包含了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的进程等。
步骤二,设当前最大运行进程数curMaxProcs=curMaxProcs+numServiceProcs(即默认最大进程数与运行Service的进程数之和),如果当前进程的数量mRemovedProcesses.size()大于这个值,则遍历所有当前运行的进程,杀死符合条件的那些进程并释放内存。进程被杀死的条件是:必须是非persistent进程,即非系统进程,必须是空进程,即进程中没有任何activity存在。如果杀死存在Activity的进程,有可能关闭用户正在使用的程序,或者使应用程序恢复的时延变大,从而影响用户体验;必须无broadcastreceiver。运行broadcastreceiver一般都在等待一个事件的发生,用户并不希望此类程序被系统强制关闭;进程中service的数量必须为0。存在service的进程很有可能在为一个或者多个程序提供某种服务,如GPS定位服务。杀死此类进程将使其他进程无法正常服务。
步骤三,再次检查当前运行的进程,如果mRemovedProcesses.size()仍然大于curMaxProcs,则放宽条件再次进行回收。
步骤四,上面3个过程都是针对整个process进行的资源回收。在以上过程执行完毕之后,将在更小的粒度上对Activity的资源进行回收。与上面所述类似,列表mLRUActivities存储了当前所有运行中的Activity,排序规则同样为最少访问原则。mLRUActivities.size()返回系统中运行的Activity的数量,当其大于MAX_ACTIVITIES(MAX_ACTIVITIES是一个常量,一般值为20,代表系统中最大允许同时存在的Activity)时。将回收部分满足条件的Activity以减少内存的使用。这里回收的只是Activity的内存资源,并不会杀死进程,也不会影响进程的运行。当进程需要调用被杀掉的Activity时,可以从保存的状态中回复,当然可能需要相对长一点的时延。
Linux内核中的内存回收
lowmemorykiller
trimApplications()函数中会执行一个叫做updateOomAdjLocked()的函数,如果返回false,则执行默认回收,若返回true则不执行默认内存回收。
updateOomAdjLocked将针对每一个进程更新一个名为adj的变量,并将其告知Linux内核,内核维护一个包含adj的数据结构(即进程表),并通过lowmemorykiller检查系统内存的使用情况,在内存不足的情况下杀死一些进程并释放内存。
由于Android操作系统中的所有应用程序都运行在独立的Dalvik虚拟机环境中,Linux内核无法获知每个进程的运行状态,也就无法为每个进程维护一个合适的adj值,因此,AndroidApplicationFramework中必须提供一套机制以动态的更新每个进程的adj。这就是updateOomAdjLocked()。
Android基于进程中运行的组件及其状态规定了默认的五个回收优先级:
IMPORTANCE_FOREGROUND:
IMPORTANCE_VISIBLE:
IMPORTANCE_SERVICE:
IMPORTANCE_BACKGROUND:
IMPORTANCE_EMPTY: