移动测试基础 对 Android Low Memory Killer 的理解

qiuijiwuhen · 2016年02月05日 · 最后由 浮云 回复于 2016年03月29日 · 3634 次阅读
本帖已被设为精华帖!

引言:
之前在测试 app 推送消息时遇到,app 点击 home 键在后台运行却接受不到推送消息,开发解释是因为手机内存不够,进程被系统杀掉了。于是上网查询了一些资料,大概整理如下:

Android 是一个多任务系统,也就是说可以同时运行多个程序,这个大家应该很熟悉。一般来说,启动运行一个程序是有一定的时间开销的,因此为了加快运行速度,当你退出一个程序时,Android 并不会立即杀掉它,这样下次再运行该程序时,可以很快的启动。随着系统中保留的程序越来越多,内存肯定会出现不足,这个时候 Android 系统开始挥舞屠刀杀程序。这里就有一个很明显的问题,杀谁?
Android 系统中杀程序的这个刽子手被称作"LowMemory Killer",它是在 Linux 内核中实现的。这里它实现了一个机制,由程序的重要性来决定杀谁。通俗来说,谁不干活,先杀谁。

一:Android 六大进程:

Android 将程序的重要性分成以下几类,按照重要性依次降低的顺序:

1.前台进程(foreground):

目前正在屏幕上显示的进程和一些系统进程。举例来说,当你运行一个程序,如浏览器,当浏览器界面在前台显示时,浏览器属于前台进程(foreground),但一旦你按 home 回到主界面,浏览器就变成了后台程序(background)。我们最不希望终止的进程就是前台进程。

2.可见进程(visible):

可见进程是一些不再前台,但用户依然可见的进程,举个例来说:widget、输入法等,都属于 visible。这部分进程虽然不在前台,但与我们的使用也密切相关,我们也不希望它们被终止(你肯定不希望时钟、天气,新闻等 widget 被终止,那它们将无法同步,你也不希望输入法被终止,否则你每次输入时都需要重新启动输入法)

3.次要服务(secondary server):

目前正在运行的一些服务(主要服务,如拨号等,是不可能被进程管理终止的,故这里只谈次要服务)简单来说就是一些杀掉了不影响系统稳定运行,但是严重影响用户使用的服务。如 GMS(GoogleMobile Service),即谷歌移动服务、拨号器等,杀掉相当影响用户使用。

4.后台进程(hidden):

虽然作者用了 hidden 这个词,但实际即是后台进程(background),就是我们通常意义上理解的启动后被切换到后台的进程,如浏览器,阅读器等。当程序显示在屏幕上时,他所运行的进程即为前台进程(foreground),一旦我们按 home 返回主界面(注意是按 home,不是按 back),程序就驻留在后台,成为后台进程

5.内容供应节点(content provider):

没有程序实体,进提供内容供别的程序去用的,比如日历供应节点,邮件供应节点等。在终止进程时,这类程序应该有较高的优先权(对于用户来说看不到只是代码里有这个)

6.空进程(empty):

没有任何东西在内运行的进程,有些程序,在程序退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息。这部分进程无疑是应该最先终止的。

二:oom_adj 和 oom_score

Low Memorry Killer 的机制主要是通过进程的 oom_adj 和 oom_score 来进行内存的处理的
 1. 每一个进程都有一个 oom_adj 值,取值范围 [-17,15]。
 2. 每一个进程都有一个 oom_scroe 值,它是根据 oom_adj 计算出一个值,分数越大越容易被杀死。
 3. 内存紧张时,LMK 基于 oom_adj 和 oom_score 值来决定是否要回收一个进程。
 4. oom_adj 值越小,越不容易被杀死,其中,-17 时 oom_score 为 0 表示不会被杀死。
 5. 查看 oom_adj 和 oom_score 方法:
cat proc/pid/oom_adj
cat proc/pid/oom_score

 六大进程分别对应的 oom_adj 值:

进程 oom_adj
FOREGROUND_APP 0
VISIBLE_APP 1
SECONDARY_SERVER 2
HIDDEN_APP 7
CONTENT_PROVIDER 14
EMPTY_APP 15

在这个表中,前面代表的是程序重要性的名称,后面的数字代表的 com_adj 的数值分配,当然了,越小的值代表程序越重要,被 Kill 的可能性也就更小。

三:LMK 的进程回收策略

Low Memory Killer Driver 在用户空间指定了一组内存临界值及与之一一对应的一组 oom_adj 值,当系统剩余内存位于内存临界值中的一个范围内时,如果一个进程的 oom_adj 值大于或等于这个临界值对应的 oom_adj 值就会进入被杀掉队列。(不同手机阀值不一样) 以华为荣耀 4 为例:

oom_adj 阈值 (页面)
0 12288
1 15360
2 18432
7 21504
14 124576
15 30720

这里其实算出来的是一个阈值,阈值的意思是当手机内存小于阈值的情况下,内存就会开始逐级回收该类型的内存了。阈值中数值的单位是内存中的页面数量,一般情况下一个页面是 4KB。比如说 15 级别是 30720 * 4K = 123 M,即当手机内存小于 123M 的时候开始回收 15 级别的应用的内存,即选择一个 oom_adj 值最大并且消耗内存最多的进程来回收:

综合上面所讲得出此表

进程 oom_adj 内存警戒值 (页面) 内存 (KB) 内存 (M)
FOREGROUND_APP 0 12288 49152 49
VISIBLE_APP 1 15360 61440 61
SECONDARY_SERVER 2 18432 73728 74
HIDDEN_APP 7 21504 86016 86
CONTENT_PROVIDER 14 24576 98304 98
EMPTY_APP 15 30720 122880 123
那么现在问题来了:

我的 app 在华为荣耀 4 上按 home 退到后台后查看的 oom_adj 的值为 4(为什么不是 7?)oom_score 的分数为 320,手机当前剩余的内存用如下命令查得:
adb shell cat /proc/meminfo
MemTotal: 1948308 kB
MemFree: 78196 kB

根据前面的理解HIDDEN_APP(86016)<剩余内存 (78196kB)<SECONDARY_SERVER(73728kB) 此时我的 app 应该被杀掉了 但实际用 adb shell ps 仍然能可以查看到我的进程。
熟悉这块的能帮我解答一下吗?哪里出现问题了,谢谢。

共收到 18 条回复 时间 点赞

这个问题问的很赞。社区榜样啊

额。。不过我有几个问题。

  1. 你荣耀 4 拿到的 oom 阀值,原本 SECONDARY_SERVER 就小于 HIDDEN_APP。一个 page 假设是一个固定值的话,那么原本这个关系就是不成立的,不知道这个你是怎么理解的

  2. 剩余内存只是 rom 本身剩余内存,那个 hidden 的应用原本内存已经是被分配了,为啥还要去和剩余内存去比?

  3. 数值你说为啥 hidden 是 4 的话,只能解释为 rom 本身的改造了。这在 Android 中也是很常见的了

/init/rc下面可以设置这个阀值的默认的oom_adj

然后通过

echo ""1111,2222,3333,4444,5555,6666">
/sys/module/lowmemorykiller/parameters/minfree

这个应该可以重新设置这个值

#2 楼 @monkey 剩余内存只是 rom 本身剩余内存,那个 hidden 的应用原本内存已经是被分配了,为啥还要去和剩余内存去比?

噢,发现了,这个就是我出问题的点了。确实不该这么比。谢谢。

@qiujiwuhen 荣耀 4 的安卓版本号多少,adj 值 (-17 ~15) ,你这个 4 应该不算低吧。stop unregisterreceiver 检查下这个。

应该和警戒内存关系不大,高于警戒是会触发 abj 值,应该是和某些地方触发了 lowmemorykiller

是我们群的秋月啊,支持下~

个人理解的 ANDROID 3.0 后的回收机制都是系统建议,实际是否回收都不怎么靠谱。
个人理解高优先级的任务在申请内存时,都是尝试申请内存,Dalvik VM 的机制在内存不足时是尝试释放内存,释不释放,什么时候释放,释放多少就都不怎么靠谱,要不然也不会有那么多 OOM。

lowmemorykiller 是根据 cache 的值触发的,cache 值低于阀值才触发 lowmemorykiller,触发时 log 中打印了阀值。例如
cache 323632kB is below limit 325000kB
实例 log 如下
<6>[28694.016131][10-30_18:37:12 utc] lowmemorykiller: Killing 'com.letv.games' (4738), adj 1000,
<6>[28694.016131][10-30_18:37:12 utc] to free 331904kB on behalf of 'kswapd0' (149) because
<6>[28694.016131][10-30_18:37:12 utc] cache 323632kB is below limit 325000kB for oom_score_adj 1000
<6>[28694.016131][10-30_18:37:12 utc] Free memory is 59388kB above reserved.
<6>[28694.016131][10-30_18:37:12 utc] Free CMA is 0kB
<6>[28694.016131][10-30_18:37:12 utc] Total reserve is 56648kB
<6>[28694.016131][10-30_18:37:12 utc] Total free pages is 59528kB
<6>[28694.016131][10-30_18:37:12 utc] Total file cache is 348292kB
<6>[28694.016131][10-30_18:37:12 utc] Slab Reclaimable is 33256kB
<6>[28694.016131][10-30_18:37:12 utc] Slab UnReclaimable is 80452kB
<6>[28694.016131][10-30_18:37:12 utc] Total Slab is 113708kB
<6>[28694.016131][10-30_18:37:12 utc] GFP mask is 0xd0

匿名 #9 · 2016年02月11日

oom_adj 的范围不应该是 [-16,16] 吗?

—— 来自 TesterHome 官方 安卓客户端

#10 楼 @lanlanxia
oom_score_adj 是–1000~1000,查了下 Linux 2.6.36 开始就替换掉了 adj,使用 oom_score_adj

#10 楼 @lanlanxia ,我文中说的 [-17,15] -17 表示禁止被 kill 掉 一般是不用的, 不过我也是根据网上搜来的数据啊。

#10 楼 @lanlanxia 请问哪个命令能打印出来这些个 log? 谢谢啊

疑问

不知道题主是从哪里获取到的 oom_adj 和进程的对应关系,从翻看 Android 源码的结果来看,大部分是和你总结的对应不上的。

oom_adj 的意义

翻看源代码可以得到如下信息

// OOM adjustments for processes in various states:

 // Adjustment used in certain places where we don't know it yet.
 // (Generally this is something that is going to be cached, but we
 // don't know the exact value in the cached range to assign yet.)
 static final int UNKNOWN_ADJ = 16;

 // This is a process only hosting activities that are not visible,
 // so it can be killed without any disruption.
 static final int CACHED_APP_MAX_ADJ = 15;
 static final int CACHED_APP_MIN_ADJ = 9;

 // The B list of SERVICE_ADJ -- these are the old and decrepit
 // services that aren't as shiny and interesting as the ones in the A list.
 static final int SERVICE_B_ADJ = 8;

 // This is the process of the previous application that the user was in.
 // This process is kept above other things, because it is very common to
 // switch back to the previous app.  This is important both for recent
 // task switch (toggling between the two top recent apps) as well as normal
 // UI flow such as clicking on a URI in the e-mail app to view in the browser,
 // and then pressing back to return to e-mail.
 static final int PREVIOUS_APP_ADJ = 7;

 // This is a process holding the home application -- we want to try
 // avoiding killing it, even if it would normally be in the background,
 // because the user interacts with it so much.
 static final int HOME_APP_ADJ = 6;

 // This is a process holding an application service -- killing it will not
 // have much of an impact as far as the user is concerned.
 static final int SERVICE_ADJ = 5;

 // This is a process with a heavy-weight application.  It is in the
 // background, but we want to try to avoid killing it.  Value set in
 // system/rootdir/init.rc on startup.
 static final int HEAVY_WEIGHT_APP_ADJ = 4;

 // This is a process currently hosting a backup operation.  Killing it
 // is not entirely fatal but is generally a bad idea.
 static final int BACKUP_APP_ADJ = 3;

 // This is a process only hosting components that are perceptible to the
 // user, and we really want to avoid killing them, but they are not
 // immediately visible. An example is background music playback.
 static final int PERCEPTIBLE_APP_ADJ = 2;

 // This is a process only hosting activities that are visible to the
 // user, so we'd prefer they don't disappear.
 static final int VISIBLE_APP_ADJ = 1;

 // This is the process running the current foreground app.  We'd really
 // rather not kill it!
 static final int FOREGROUND_APP_ADJ = 0;

 // This is a process that the system or a persistent process has bound to,
 // and indicated it is important.
 static final int PERSISTENT_SERVICE_ADJ = -11;

 // This is a system persistent process, such as telephony.  Definitely
 // don't want to kill it, but doing so is not completely fatal.
 static final int PERSISTENT_PROC_ADJ = -12;

 // The system process runs at the default adjustment.
 static final int SYSTEM_ADJ = -16;

 // Special code for native processes that are not being managed by the system (so
 // don't have an oom adj assigned by the system).
 static final int NATIVE_ADJ = -17;

可以看到 oom_adj=4 的进程是系统尽量保护的进程。

lowmemorykiller 实现原理

这里给出 lowmemorykiller 的源码,题主可以去看看它的实现
https://android.googlesource.com/kernel/common.git/+/android-3.4/drivers/staging/android/lowmemorykiller.c

精力有限,暂时看了这些源码,有兴趣的可以一起学习交流。

#14 楼 @hqzhon 你的这个是干货, 对应关系是在网上找的资料。

赞~需要细细品尝

#9 楼 @sandman 您好,请问该 log 是在哪里查看的?

#17 楼 @heranjin 发生 lowmemorykiller kernel 打印的 log,我只是举了个实际发生时的实例。

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册