嵌入式装备的1个普遍特点是内存容量相对有限。当运行的程序超过1定数量时,或触及复杂的计算时,极可能出现内存不足,进而致使系统卡顿的现象。Android 系统也不例外,它一样面临着装备物理内存短缺的窘境。对已启动过1次的Android程序,再1次启动所花的时间会明显减少。缘由在于Android系统其实不马上清算那些已”淡出视野”的程序(比如你调用Activity.finish退出UI界面)。它们在1定的时间里依然驻留在内存中。这样做的好处是明显的,即下1次启动不需要再为程序重新创建1个进程;坏处就是,加大了内存OOM的几率。
Android是基于Linux的,而Linux底层内核有自己的内存监控机制,即OOMKiller。1旦发现系统的可用内存到达临界值,这个OOM的管理者就会自动跳出来清算内存。
OOMKiller有不同的策略和不同的处理手段。它的核心思想以下:
依照优先级顺序,从低到高逐渐杀掉进程,回收内存。
优先级的设定策略主要斟酌两个方面:1个是要斟酌对系统的侵害程度(例如系统的核心进程,优先级通常较高),另外一方面也希望尽量多地释放无用内存。1个公道的策略最少需要斟酌以下几个因素:
内核所管理的进程都有1个衡量其oom权重的值,存储在/proc/< PID >/oom_adj中。根据这1权重值和上面所提及的若干其他因素,系统会实时给每一个进程评分,以决定OOM时应当杀死哪些进程。
这个值存储在/proc/< PID >/oom_score中。
oom_score分数越低的进程,被杀死的几率越小,或说被杀死的时间越晚。
下面展现了PID为5912的NetworkManager进程的oom_adj 和oom_score,可以看到分数很低,说明此进程10分重要,1般不会被系统杀死。
基于Linux内核OOM Killer的核心思想,Android 系统扩大出了自己的内存监控体系。由于Linux下的内存杀手需要等到系统资源”濒临绝境”的情况下才会产生效果,而Android则实现了自己的Killer.
Android 系统为此开发了1个专门的驱动,名为Low Memory Killer(LMK)。源码路径在内核工程的 drivers/staging/android/Lowmemorykiller.c中。
它的驱动加载函数以下:
static int __init lowmem_init(void)
{
register_shrinker(&lowmem_shrinker);
return 0;
}
可见LMK向内核线程注册了1个shrinker的监听回调,实现体为lowmem_shrinker。当系统的空闲页面低于1定阈值时,这个回调就会被履行。
Lowmemorykiller.c 中定义了两个数组,分别以下:
static short lowmem_adj[6] = {
0,
1,
6,
12,
};
static int lowmem_adj_size = 4;//下面的数值以此为单位(页大小)
static int lowmem_minfree[6] = {
3 * 512, /* 6MB */
2 * 1024, /* 8MB */
4 * 1024, /* 16MB */
16 * 1024, /* 64MB */
};
第1个数组lowmem_adj最多有6个元素,默许只定义了4个,它表示可用容量处于”某层级”时需要被处理的adj值;第2个数组则是对”层级”的描写。这样说可能不清楚,举个例子,lowmem_minfree 的第1个元素是3*512,3*512*lowmem_adj_size=6MB.意味着当可用内存小于6MB时,Killer需要清算adj的值为0(即lowmem_adj的第1个元素)以下的那些进程。其中adj的取值范围是⑴7~15,数字越小表示进程级别越高,通常只有0⑴5被使用。
下图是LWK机制的实现简图。
这两个数组只是系统的预定义值,我们可以根据项目的实际需求来定制。
Android系统提供了相应的文件来供我们修改这两组值。
路径以下:
/sys/module/lowmemorykiller/parameters/adj
/sys/module/lowmemorykiller/parameters/minfree
可以在 init.rc(系统启动时,由init进程解析的第1个脚本)中加入以下语句。init.rc路径为/system/core/rootdir/init.rc
/sys/module/lowmemorykiller/parameters/adj 0,8
/sys/module/lowmemorykiller/parameters/minfree 1024,4096
进程omm_adj的大小跟进程的类型和进程被调度的次序有关。
在Android中,进程主要分为以下几个种类:
1. 前台进程(foreground)
目前正在屏幕上显示的进程和1些系统进程。举例来讲,Dialer,Storage,Google Search等系统进程就是前台进程;再举例来讲,当你运行1个程序,如阅读器,当阅读器界面在前台显示时,阅读器属于前台进程(foreground),但1旦你按home回到主界面,阅读器就变成了后台程序(background)。我们最不希望终止的进程就是前台进程。
2. 可见进程(visible)
可见进程是1些不再前台,但用户仍然可见的进程,举个例来讲:widget、输入法等,都属于visible。这部份进程虽然不在前台,但与我们的使用也密切相干,我们也不希望它们被终止(你肯定不希望时钟、天气,新闻等widget被终止,那它们将没法同步,你也不希望输入法被终止,否则你每次输入时都需要重新启动输入法)。
3. 桌面进程(home app)
即launcher,保证在多任务切换以后,可以快速返回到home界面而不需重新加载launcher。
4. 次要服务(secondary server)
目前正在运行的1些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务),举例来讲:谷歌企业套件,Gmail内部存储,联系人内部存储等。这部份服务虽然属于次要服务,但很1些系统功能仍然息息相干,我们经常需要用到它们,所以也不太希望他们被终止。
5. 后台进程(hidden)
即是后台进程(background),就是我们通常意义上理解的启动后被切换到后台的进程,如阅读器,浏览器等。当程序显示在屏幕上时,他所运行的进程即为前台进程(foreground),1旦我们按home返回主界面(注意是按home,不是按back),程序就驻留在后台,成为后台进程(background)。后台进程的管理策略有多种:有较为积极的方式,1旦程序到达后台立即终止,这类方式会提高程序的运行速度,但没法加速程序的再次启动;也有较消极的方式,尽量多的保存后台程序,虽然可能会影响到单个程序的运行速度,但在再次启动已启动的程序时,速度会有所提升。这里就需要用户根据自己的使用习惯找到1个平衡点。
6. 内容供应节点(content provider)
没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应当有较高的优先权。
7. 空进程(empty)
没有任何东西在内运行的进程,有些程序,比如BTE,在程序退出后,仍然会在进程中驻留1个空进程,这个进程里没有任何数据在运行,作用常常是提高该程序下次的启动速度或记录程序的1些历史信息。这部份进程无疑是应当最早终止的。
在AMS 用于处理进程的相干代码文件ProcessList.java 中,定义了不同类型的adj值,以下所示:
/**
*省略其它代码
*/
//未知的adj
static final int UNKNOWN_ADJ = 16;
static final int CACHED_APP_MAX_ADJ = 15;
static final int CACHED_APP_MIN_ADJ = 9;
//B list of service ,和A list相比,对用户黏合度小1些
static final int SERVICE_B_ADJ = 8;
//用户前1次交互的进程
static final int PREVIOUS_APP_ADJ = 7;
//Launcher进程
static final int HOME_APP_ADJ = 6;
//运行了application service的进程
static final int SERVICE_ADJ = 5;
//重量级利用程序进程
static final int HEAVY_WEIGHT_APP_ADJ = 4;
//用于承载backup相干操作的进程
static final int BACKUP_APP_ADJ = 3;
//这类进程能被用户感觉到但不可见,如后台运行的音乐播放器
static final int PERCEPTIBLE_APP_ADJ = 2;
//有前台可见的Activity进程,如果轻易杀死这类进程,将严重影响用户体验
static final int VISIBLE_APP_ADJ = 1;
//当前正在运行的那个进程,即用户正在交互的程序
static final int FOREGROUND_APP_ADJ = 0;
static final int PERSISTENT_SERVICE_ADJ = -11;
//persistent性质的进程,如telephony
static final int PERSISTENT_PROC_ADJ = -12;
//系统进程
static final int SYSTEM_ADJ = -16;
/**
*省略其它代码
*/
上面定义的adj数值来看,adj越小表示进程类型就越重要,特别的,系统进程的默许oom_adj 为⑴6,这类进程永久不会被杀死。
还需要注意的是updateOomLevels函数,内部原理是通过写上面两个文件来实现,AMS 会根据系统确当前配置自动修正adj和minfree,以尽量适配不同的硬件。函数源码以下所示:
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 = 480*800; // 384000
int maxSize = 1280*800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
if (false) {
Slog.i("XXXXXX", "scaleMem=" + scaleMem);
Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth
+ " dh=" + displayHeight);
}
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
if (scale < 0) scale = 0;
else if (scale > 1) scale = 1;
int minfree_adj = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust);
int minfree_abs = Resources.getSystem().getInteger(
com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute);
if (false) {
Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs);
}
// We've now baked in the increase to the basic oom values above, since
// they seem to be useful more generally for devices that are tight on
// memory than just for 64 bit. This should probably have some more
// tuning done, so not deleting it quite yet...
final boolean is64bit = false; //Build.SUPPORTED_64_BIT_ABIS.length > 0;
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
mOomMinFree[i] = (int)(low + ((high-low)*scale));
if (is64bit) {
// On 64 bit devices, we consume more baseline RAM, because 64 bit is cool!
// To avoid being all pagey and stuff, scale up the memory levels to
// give us some breathing room.
mOomMinFree[i] = (3*mOomMinFree[i])/2;
}
}
if (minfree_abs >= 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] = (int)((float)minfree_abs * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
}
}
if (minfree_adj != 0) {
for (int i=0; i<mOomAdj.length; i++) {
mOomMinFree[i] += (int)((float)minfree_adj * mOomMinFree[i]
/ mOomMinFree[mOomAdj.length - 1]);
if (mOomMinFree[i] < 0) {
mOomMinFree[i] = 0;
}
}
}
// The maximum size we will restore a process from cached to background, when under
// memory duress, is 1/3 the size we have reserved for kernel caches and other overhead
// before killing background processes.
mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3;
// Ask the kernel to try to keep enough memory free to allocate 3 full
// screen 32bpp buffers without entering direct reclaim.
int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
if (reserve_abs >= 0) {
reserve = reserve_abs;
}
if (reserve_adj != 0) {
reserve += reserve_adj;
if (reserve < 0) {
reserve = 0;
}
}
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i++) {
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
writeLmkd(buf);
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
}
// GB: 2048,3072,4096,6144,7168,8192
// HC: 8192,10240,12288,14336,16384,20480
}
除系统的评定标准,我们也能够用自己的方式来改变进程的权重值。
1.写文件
和上述提到的adj和minfree类似,进程的oom_adj也能够通过写文件的情势来修改,路径为/proc/< PID >/oom_adj。比如init.rc中有以下语句
on early-init
# Set init and its forked children's oom_adj.
write /proc/1/oom_adj ⑴6
PID 为1的进程为init程序,将此进程的oom_adj 设置为⑴6,保证它不会被杀死。
2.设置android:persistent
对某些非常重要的程序,不希望它被系统杀死。在AndroidMainifest.xml文件中给application 标签添加”android:persistent=true”属性,将利用程序设置为常驻内存,但需要特别注意,如果利用程序本不够完善,而系统又不能正常回收,那末会致使意想不到的问题。
参考资料
《深入理解Android内核设计思想》