引言:
之前在测试 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 仍然能可以查看到我的进程。
熟悉这块的能帮我解答一下吗?哪里出现问题了,谢谢。


↙↙↙阅读原文可查看相关链接,并与作者交流