Android 是一款基于 Linux 内核,面向移动终端的操做系统。为适应其做为移动平台操做系统的特殊须要,谷歌对其作了特别的设计与优化,使得其进程调度与资源管理与其余平台的 Linux 有明显的区别。主要包含下面几个层次:html
Application Framework 将整个操做系统分隔成两个部分。对应用开发者而言,全部 APP 都是运行在 Application Framework 之上,而并不须要关心系统底层的状况。Application Framework 层为应用开发者提供了丰富的应用编程接口,如 Activity Manager,Content Provider,Notification Manager,以及各类窗口 Widget 资源等。在 Application Framework 层,Activity 是一个 APP 最基本的组成部分。通常每一个 Activity 对应于屏幕上的一个视图(或者说一屏),一个 APP 能够有一个或者多个 Activity。应用程序被打包成 .apk 格式的文件,由 Dalvik VM 解释执行。java
Dalvik 虚拟机采用寄存器架构,而不是 JVM 的栈结构。Java 程序编译后的 .class 文件并不能在 Dalvik 中解释执行。所以 Google 提供了一个 dx 工具,用于将 .class 文件转换成 Dalivk 可以识别的 .dex 格式。具体 Dalvik VM 的细节不是本文重点,如下再也不讨论。linux
由上所述,全部的 APP 都是由 Java 代码编写并在 Dalvik VM 中获得解释执行。在 Android 操做系统中,每一个 Dalvik VM 的每一个 Instance 都对应于 Linux 内核中的一个进程。可使用 adb shell 工具查看系统中的当前进程。以下图所示,Android2.3.3 启动后内核中的进程列表。android
图 1 中,UID 标识为 app_xx 的每一项都是一个 app 所占用的进程,可见 Android 设计使得每一个应用程序由一个独立的 Dalvik 实例解释执行,而每一个 Linux 内核进程加载一个 Dalvik 实例,经过这种方式提供 app 的运行环境。如此,每一个 APP 的资源被彻底屏蔽,互不干扰。虽然同时引入了进程间通讯的困难,但也带来了更强的安全性。shell
下面将从 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文件中。
ActivityManagerService 集中管理全部进程的内存资源分配。全部进程须要申请或释放内存以前必须调用 ActivityManagerService 对象,得到其“许可”以后才能进行下一步操做,或者 ActivityManagerService 将直接“代劳”。类 ActivityManagerService 中涉及到内存回收的几个重要的成员方法以下:trimApplications(),updateOomAdjLocked(),activityIdleInternal() 。这几个成员方法主要负责 Android 默认的内存回收机制,若 Linux 内核中的内存回收机制没有被禁用,则跳过默认回收。
Android 操做系统中的内存回收可分为两个层次,即默认内存回收与内核级内存回收,本章重点对默认内存回收机制进行研究,Linux 内核层次的内存回收机制将在下一张介绍。 本章全部代码可参见 ActivityManagerService.java。
Android 系统中内存回收的触发点大体可分为三种状况。第一,用户程序调用 StartActivity(), 使当前活动的 Activity 被覆盖;第二,用户按 back 键,退出当前应用程序;第三,启动一个新的应用程序。这些可以触发内存回收的事件最终调用的函数接口就是 activityIdleInternal()。当 ActivityManagerService 接收到异步消息 IDLE_TIMEOUT_MSG 或者 IDLE_NOW_MSG 时,activityIdleInternal() 将会被调用。代码以下:
case IDLE_NOW_MSG:{ IBinder token = (Ibinder)msg.obj; activityIdle(token, null); } break;
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;
IDLE_NOW_MSG 由 Activity 的切换以及 Activiy 焦点的改变等事件引起,IDLE_TIMEOUT_MSG 在 Activity 启动超时的状况下引起,通常这个超时时间设为 10s,若是 10s 以内一个 Activity 依然没有成功启动,那么将发送异步消息 IDLE_TIMEOUT_MSG 进行资源回收。activityIdleInternal() 的主要任务是改变系统中 Activity 的状态信息,并将其添加到不一样状态列表中。其主要工做以下:
首先,调用 scheduleAppGcsLocked() 方法通知全部进行中的任务进行垃圾回收。scheduleAppGcsLocked() 将进行调度 JVM 的 garbage collect,回收一部份内存空间,这里仅仅是通知每一个进程自行进程垃圾检查并调度回收时间,而非同步回收。而后,取出 mStoppingActivities 和 mFinishigActivities 列表中的全部内容,暂存在临时变量中。这两个列表分别存储了当前状态为 stop 和 finishi 的 activity 对象。对于 stop 列表,若是其中的 activity 的 finish 状态为 true,判断是否是要当即中止,若是要当即中止则调用 destroyActivityLocked() 通知目标进程调用 onDestroy() 方法,不然,先调用 resumeTopActivity() 运行下一个 Activity。若是 finish 状态为 false,则调用 stopActivityLocked() 通知客户进程中止该 Activity,这种状况通常发生在调用 startActivity() 后。对于 finish 列表,直接调用 destroyActivityLocked() 通知客户进程销毁目标 Activity。
这里的 destroyActivityLocked 等函数并无真正意义上改变内存的使用,只是将其状态改变为“容许回收”,真正的回收在下面即将调用的 trimApplications() 函数中。
trimApplications() 函数的结构以下 :
private final void trimApplications() { synchronized (this) { // First remove any unused application processes 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, try to // finish as many as we can to get back down to the limit. (3)do something } }
清单 3 中的三个标序号的位置分别负责以下工做:
(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 的进程,运行 broadcast receiver 的进程等,见以下代码。
if (app.persistent || app.services.size() != 0 || app.curReceiver != null || app.persistentActivities > 0) { // Don't count processes holding services against our // maximum process count. numServiceProcs++; }
步骤二, 设当前最大运行进程数 curMaxProcs = curMaxProcs + numServiceProcs(即默认最大进程数与运行 Service 的进程数之和),若是当前进程的数量 mRemovedProcesses.size() 大于这个值,则遍历全部当前运行的进程,杀死符合条件的那些进程并释放内存。清理过程见清单 5(部分代码省略)。从清单 5 的代码中能够看出,进程被杀死的条件是:
以上条件缺一不可。
if (!app.persistent && app.activities.size() == 0 && app.curReceiver == null && app.services.size() == 0) { if (app.pid > 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } else { try { app.thread.scheduleExit(); } catch (Exception e) { // Ignore exceptions. } } // todo: For now we assume the application is not buggy // or evil, 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--; }
步骤三,再次检查当前运行的进程,若是 mRemovedProcesses.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 的状态已经保存,当前处在不可见状态而且 Activity 已经 Stop。这时杀掉进程只会下降下次调用程序时的加载速度,下次启动时将恢复到关闭以前的状态,并不会在用户体验上形成致命的影响,因此,canQuit 置位为真。这种状况与步骤二的回收方式也有所不一样,因为进程中 Activity 的数量不是 0,下一步须要对每一个 activity 执行 destroyActivityLocked() 销毁,最后才杀死进程。
boolean canQuit = !app.persistent && app.curReceiver == null && app.services.size() == 0 && app.persistentActivities == 0; int NUMA = app.activities.size(); for (j=0; j<NUMA && canQuit; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); canQuit = (r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped; } if (canQuit) { // Finish all of the activities, and then the app itself. for (j=0; j<NUMA; j++) { HistoryRecord r = (HistoryRecord)app.activities.get(j); if (!r.finishing) { destroyActivityLocked(r, false); } r.resultTo = null; } if (app.pid > 0 && app.pid != MY_PID) { Process.killProcess(app.pid); } cleanUpApplicationRecordLocked(app, false, i); i--; //dump(); }
步骤四,上面 3 个过程都是针对整个 process 进行的资源回收。在以上过程执行完毕以后,将在更小的粒度上对 Activity 的资源进行回收。与上面所述相似,列表 mLRUActivities 存储了当前全部运行中的 Activity,排序规则一样为最少访问原则。mLRUActivities.size() 返回系统中运行的 Activity 的数量,当其大于 MAX_ACTIVITIES(MAX_ACTIVITIES 是一个常量,通常值为 20,表明系统中最大容许同时存在的 Activity)时。将回收部分知足条件的 Activity 以减小内存的使用。回收条件代码清单 7 所示:
//Finally, if there are too many activities now running, try to // finish as many as we can to get back down to the limit. for ( i=0; i<mLRUActivities.size() && mLRUActivities.size() > curMaxActivities; i++) { final HistoryRecord r = (HistoryRecord)mLRUActivities.get(i); // We can finish this one if we have its icicle saved and // it is not persistent. if ((r.haveState || !r.stateNotNeeded) && !r.visible && r.stopped && !r.persistent && !r.finishing) { final int origSize = mLRUActivities.size(); destroyActivityLocked(r, true); if (origSize > mLRUActivities.size()) { i--; } } }
这里回收的只是 Activity 的内存资源,并不会杀死进程,也不会影响进程的运行。当进程须要调用被杀掉的 Activity 时,能够从保存的状态中回复,固然可能须要相对长一点的时延。
上面提到,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 首先调用 computeOomAdjLocked() 初步计算 adj 的值,而后回到 updateOomAdjLocked() 对其值进行进一步修正。估算流程可参见代码。
关于 adj,其定义在 task_struct->signal_struct->adj, 文件 /kernel/include/linux/sched.h 中。实质为进程数据结构中的一个变量,用来表示发生 Out of Memory 时杀死进程的优先级顺序。lowmemorykiller 利用这个变量对进程的重要程度进行判断,并在内存不足时释放部分空间,其实如今文件 /kernel/drivers/staging/android/lowmemorykiller.c 中。lowmemorykiller 定义了两个数组:lowmem_adj 和 lowmem_minfree。其中 lowmen_adj 定义了一系列 adj 键值,而 lowmem_minfree 的每一个元素表明一个内存阈值。以下代码中四个阈值分别是 6MB,8MB,16MB 和 64MB,分别表明当内存小于 64MB 时,adj 大于或等于 12 的那些进程将被杀死并回收,内存小于 16MB 时,adj 大于等于 6 的那些进程将被杀死并回收,内存小于 8MB 时,adj 大于等于 1 的那些进程将被杀死并回收,内存小于 6MB 时,adj 大于等于 0 的全部进程将被杀死并回收。内核中的每一个进程都持有一个 adj,取值范围 -17 到 15,值越小表明进程的重要性越高,回收优先级越低,其中 -17 表明禁用自动回收。Android 系统中,只有 0-15 被使用。
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 lowmem_minfree_size = 4;
lowmemorykiller 注册一个 lowmem_shrinker,lowmem_shrinker 利用了标准 Linux 内核中的 Cache Shrinker 来实现,当空闲内存页面不足时,内核线程 kswapd 将用已注册的 lowmem_shrinker 来回收内存页面。
static struct shrinker lowmem_shrinker = { .shrink = lowmem_shrink, .seeks = DEFAULT_SEEKS * 16 }; static int __init lowmem_init(void) { task_free_register(&task_nb); register_shrinker(&lowmem_shrinker); return 0; }
lowmem_shrink 的代码在函数 lowmem_shrink 中,下面给出该函数的主要结构。lowmem_shrink 根据上述规则遍历全部进程,选出须要结束的进程,经过发送一个没法忽略的信号 SIGKILL 强制结束这些进程
static int lowmem_shrink(struct shrinker *s, int nr_to_scan, gfp_t gfp_mask) { for_each_process(p) { //Select processes to be forced } if (selected) { force_sig(SIGKILL, selected); rem -= selected_tasksize; } else rem = -1; return rem; }
若是上述各类方法都没法释放出足够的内存空间,那么当为新的进程分配应用程序时将发生 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 的声明以下:
unsigned long badness(struct task_struct *p, unsigned long uptime) 函数 select_bad_process 返回将要杀掉的那个进程。
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 (points > *ppoints || !chosen) { chosen = p; *ppoints = points; } } return chosen; }
最后,和 lowmemorykiller 同样,经过发送 SIGKILL 结束选中的进程。因为 oom_killer 与标准 Linux 内核并没有不一样,这里再也不详细研究。
本文研究了 Android 操做系统上的内存回收机制。主要包括 Application Framework 层的默认回收以及 Linux 内核中的 lowmemorykiller、OOM_killer。通常来讲应用开发者并不须要控制或者修改系统的内存管理以及回收,可是深刻理解这些系统级的管理机制仍是必要的,尤为有助于更加合理地设计应用程序,使应用程序的进程在其生命周期内高效地运行。而系统级开发者若是想要对内存管理机制进行优化,对原有机制的理解则是必不可少的重要前提。