程序员人生 网站导航

Android 内存管理机制详解

栏目:综合技术时间:2016-12-06 10:38:45

  嵌入式装备的1个普遍特点是内存容量相对有限。当运行的程序超过1定数量时,或触及复杂的计算时,极可能出现内存不足,进而致使系统卡顿的现象。Android 系统也不例外,它一样面临着装备物理内存短缺的窘境。对已启动过1次的Android程序,再1次启动所花的时间会明显减少。缘由在于Android系统其实不马上清算那些已”淡出视野”的程序(比如你调用Activity.finish退出UI界面)。它们在1定的时间里依然驻留在内存中。这样做的好处是明显的,即下1次启动不需要再为程序重新创建1个进程;坏处就是,加大了内存OOM的几率。

Linux内存监控机制(OOMKiller)

  Android是基于Linux的,而Linux底层内核有自己的内存监控机制,即OOMKiller。1旦发现系统的可用内存到达临界值,这个OOM的管理者就会自动跳出来清算内存。

OOMKiller有不同的策略和不同的处理手段。它的核心思想以下:

依照优先级顺序,从低到高逐渐杀掉进程,回收内存。

优先级的设定策略主要斟酌两个方面:1个是要斟酌对系统的侵害程度(例如系统的核心进程,优先级通常较高),另外一方面也希望尽量多地释放无用内存。1个公道的策略最少需要斟酌以下几个因素:

  • 进程消耗的内存
  • 进程占用的CPU时间
  • oom_adj(OOM权重)

  内核所管理的进程都有1个衡量其oom权重的值,存储在/proc/< PID >/oom_adj中。根据这1权重值和上面所提及的若干其他因素,系统会实时给每一个进程评分,以决定OOM时应当杀死哪些进程。
这个值存储在/proc/< PID >/oom_score中。
oom_score分数越低的进程,被杀死的几率越小,或说被杀死的时间越晚。
下面展现了PID为5912的NetworkManager进程的oom_adj 和oom_score,可以看到分数很低,说明此进程10分重要,1般不会被系统杀死。

oom_score

Android 内存管理机制

基于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机制的实现简图。

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

Android进程分类

进程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 }

改变进程权重adj值

除系统的评定标准,我们也能够用自己的方式来改变进程的权重值。

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内核设计思想》

------分隔线----------------------------
------分隔线----------------------------

最新技术推荐