简述 Android Low Memory Killer Daemon (lmkd)

阅读前提

  • 有一定 Android 开发经验,较好的 Android 基础知识
  • 了解常用 Linux 命令

说明

  本文涉及到的内容和 Android 版本有很大关系。网上大家能查到的很多是基于 Andorid 6(含)之前的,由于现在 Android 6 的设备市面上已经很少了,所以这里就不详细讲了。本文主要讲解从 Android 7 开始,LMK 的处理机制。

  本文只做简要介绍,不做详细分析。

背景

  最近在调查 Android Low Memory Killer 原理,以前一直不关心这个问题,因此系统都已经自己处理了。最近的项目使用的手机内存只有 2G,系统是 Android 7.1.2,同时运行的应用一多(大约5~6款占内存较多的 App),就会出现 ANR 或者 Crash 的现象。当时就在想,按道理系统在内存不足时应该自己会释放一些应用资源,不应该频繁出现 ANR 或者 Crash 的问题。但是为什么实际情况是总出问题呢?为了调查原因,就对系统的内存释放原理做了些调查。

前言

  为了避免 Cached Pages 太少时导致设备卡顿、死机、重启等情况,Android 引入了 Low Memory Killer(源自 Linux OOM Killer)机制,提前回收优先级比较低的进程所占的资源,以保证一个较好的用户体验。进程优先级列表由 SystemServer 进程维护。详见后文“low-memory killer (LMK) Kill 顺序”中的图表。

关于“内存压力”(Memory pressure)

  在 Android 系统中,多个进程是同时运行的。但是在内存不足的情况下,程序的运行就可能出现问题。例如,程序运行速度明显变慢,或者运行时会出错。“内存压力”(Memory pressure)是指系统在内存不足情况下的一种运行状态,需要释放内存来缓解这种压力。通过杀死不重要的进程,或者释放不重要的缓存资源等来达到此目的。
  
  起初,Android 使用内核 lowmemorykiller 驱动程序来监视系统内存压力,该驱动程序是一种依赖于硬编码值的严格机制。 从 kernel 4.12 开始,lowmemorykiller 驱动程序已从上游内核中被删除了,取而代之的是使用 lmkd 来进行内存监视和进程终止任务。
  
  较新的手机其内核一般都是 4.12 以上的。以我手里的手机为例:

  • 华为荣耀 V20 (Android 10.0) 内核版本: 4.14.116
  • 三星 Galaxy S7 edge (Android 8.0)内核版本: 3.18.71-14176914
  • Google Nexus 6 (Android 7.1.1) 内核版本: 3.10.40-gc7ebca13933

    什么是 Low Memory Killer Daemon (lmkd)

  Andorid 的 Low Memory Killer 是在标准的 Linux Kernel 的 OOM Killer 基础上修改而来的一种内存管理机制。当系统内存不足时,杀死不重要的进程以释放其内存。

  Low Memory Killer Daemon (lmkd) 就是 LMK 守护进程,随系统启动。

  lmkd 实现了与内核驱动程序相同的功能,但是使用现有的内核机制来检测和估计内存压力。 这些机制包括使用内核生成的 vmpressure 事件或 Pressure stall information(PSI)Monitor 来获取有关内存压力级别的通知,并使用内存 cgroup 机制根据进程的重要性来限制分配给每个进程的内存资源。

LMK 的关键参数

  • oom_adj:在 Framework 层使用,代表进程的优先级。数值越高,优先级越低,越容易被清理。取值范围 [-17, 16],实现可使用的范围是 [-16, 15]。是给用户来配置进程优先级的。为了方便用户配置,提供了范围更小的 oom_adj 参数。需要注意的是,-17 代表 Native 进程,是不会被 OOMKiller 清理的。16 代表即将被缓存的进程,但是并不确定其具体的 oom_adj 值。
  • oom_adj threshold:在 Framework 层使用,代表 oom_adj 的内存阈值。Android Kernel 会定期检测当前剩余内存是否低于这个阀值,若低于则清理 oom_adj ≥ 该阈值对应的 oom_adj 中数值最大的进程,直到剩余内存恢复至高于该阀值的状态。
  • oom_score:是进程的最终得分,清理进程时实际使用的参数。分数越高,越容易被清理。
  • oom_adj_score(实际情况也有叫 oom_score_adj 的):在 Kernel 层使用,取值范围:[-1000, 1001]。是 kernel 用来配置进程优先级的。数值越低,最终的 oom_score 越低。该值由 oom_adj 换算而来。

清理过程

  当内存耗尽的时候,OOMKiller 会调用 out_of_memory()select_bad_process()oom_score 最大的值就是那个将要被杀死的 bad process。oom_badness()oom_score_adj 作为基础值,根据是否为管理员进程,占用的内存情况,来计算出最终的 oom_score 值,分值越高,越容易被杀死。

OOMKiller 源码

  相应的源码文件如下(以 v4.14.116 为例):
/fs/proc/base.c
/mm/oom_kill.c

查看 oom_adj 相关值的方法

  通过 ps 命令查到指定进程的进程 ID,然后在 /proc/<process id> 目录下,查看指定文件: oom-adj

注意:需要 Root 权限才能查看以上值。
具体值含义下文会讲到。

LMK 各关键参数补充

oom_adj

  取值范围 [-17, 16],实现可使用的范围是 [-16, 15]。具体的大家可以参看该文件。-17 代表 Native 进程,是不会被 OOMKiller 清理的。16 代表即将被缓存的进程,但是并不确定其具体的 oom_adj 值。
  Android 6 之前(含 Android 6),以 Android 6.0 为例,可以通过如下文件 ProcessList.java 快速了解其具体值并作为参考使用。
  不过需要注意的是, Android 7 及之后的版本,该文件发生了变化,其值含义代表的是 oom_adj_score
  在 Linux Kernel 中,说明了其真正的取值范围。详见 oom.h 文件。这里以 4.14.116 版本为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _UAPI__INCLUDE_LINUX_OOM_H
#define _UAPI__INCLUDE_LINUX_OOM_H

/*
* /proc/<pid>/oom_score_adj set to OOM_SCORE_ADJ_MIN disables oom killing for
* pid.
*/
#define OOM_SCORE_ADJ_MIN (-1000)
#define OOM_SCORE_ADJ_MAX 1000

/*
* /proc/<pid>/oom_adj set to -17 protects from the oom killer for legacy
* purposes.
*/
#define OOM_DISABLE (-17)
/* inclusive */
#define OOM_ADJUST_MIN (-16)
#define OOM_ADJUST_MAX 15

#endif /* _UAPI__INCLUDE_LINUX_OOM_H */

  oom_adj 的初始化,更新及进程的移除,都与 ActivityManagerService 有关(来自 AOSP master 分支)。其中定义了进程状态值:

常量定义 常量取值 含义
PROCESS_STATE_UNKNOWN -1 非真实的进程状态
PROCESS_STATE_PERSISTENT 0 persistent 系统进程
PROCESS_STATE_PERSISTENT_UI 1 persistent 系统进程,并正在执行 UI 操作
PROCESS_STATE_TOP 2 拥有当前用户可见的 Top Activity
PROCESS_STATE_FOREGROUND_SERVICE_LOCATION 3 持有一个前台 Service 的进程并带有位置类型
PROCESS_STATE_BOUND_TOP 4 绑定到 Top 应用的进程。
PROCESS_STATE_FOREGROUND_SERVICE 5 持有一个前台 Service 的进程
PROCESS_STATE_BOUND_FOREGROUND_SERVICE 6 由于系统绑定,持有一个前台 Service 的进程
PROCESS_STATE_IMPORTANT_FOREGROUND 7 对用户很重要,并且能被察觉到的进程
PROCESS_STATE_IMPORTANT_BACKGROUND 8 对用户很重要,但是不一定能被察觉到的进程
PROCESS_STATE_TRANSIENT_BACKGROUND 9 后台瞬时进程,尽量保持其处于运行状态
PROCESS_STATE_BACKUP 10 在后台运行备份及恢复的进程
PROCESS_STATE_SERVICE 11 进程中有后台 Service 正处于运行状态或处于执行操作状态
PROCESS_STATE_RECEIVER 12 正在后台运行 receiver 的进程
PROCESS_STATE_TOP_SLEEPING 13 与 PROCESS_STATE_TOP 一样,只不过设备正处于睡眠状态
PROCESS_STATE_HEAVY_WEIGHT 14 后台进程,但无法恢复到之前的状态。因此尽量避免清理该进程
PROCESS_STATE_HOME 15 后台进程,但持有 Home 应用
PROCESS_STATE_LAST_ACTIVITY 16 后台进程,且拥有上一次显示的 Activity
PROCESS_STATE_CACHED_ACTIVITY 17 以供以后使用的被缓存的进程,且内含 Activity
PROCESS_STATE_CACHED_ACTIVITY_CLIENT 18 以供以后使用的被缓存的进程,且为另一个缓存进程(内含 Activity)的 client 进程
PROCESS_STATE_CACHED_RECENT 19 以供以后使用的被缓存的进程,且内含与当前最近任务相对应的 Activity
PROCESS_STATE_CACHED_EMPTY 20 以供以后使用的被缓存的进程,且为空进程
PROCESS_STATE_NONEXISTENT 21 不存在的进程

  同一进程,在不同状态下,其 oom_adj 是不一样的。

oom_adj threshold

  ProcessList.java (来自 AOSP master 分支)。该文件同样定义了 oom_adj 的阈值范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// These are the various interesting memory levels that we will give to
// the OOM killer. Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
PERCEPTIBLE_LOW_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
};

// The actual OOM killer memory levels we are using.
private final int[] mOomMinFree = new int[mOomAdj.length];

// These are the low-end OOM level limits. This is appropriate for an
// HVGA or smaller phone with less than 512MB. Values are in KB.
private final int[] mOomMinFreeLow = new int[] {
12288, 18432, 24576,
36864, 43008, 49152
};

// These are the high-end OOM level limits. This is appropriate for a
// 1280x800 or larger screen with around 1GB RAM. Values are in KB.
private final int[] mOomMinFreeHigh = new int[] {
73728, 92160, 110592,
129024, 147456, 184320
};

内存阈值查看方法
1
# cat /sys/module/lowmemorykiller/parameters/minfree

oom-adj-threshold

结果中数值的单位是 page(1 page = 4K)。ProcessList.java 文件(来自 AOSP master 分支)中也有提到:

1
2
// Memory pages are 4K.
static final int PAGE_SIZE = 4 * 1024;

以上值对应的分别是 72M,90M,108M,126M,144M,180M。以本例为例,当内存小于 180M 时,系统就要开始清理内存了。

查看进程等级阈值
1
# cat /sys/module/lowmemorykiller/parameters/adj

oom-adj-threshold-adj

oom_adj_score

  实际情况也有叫 oom_score_adj 的。我手边各大品牌及型号的手机还算挺全,我看到的都是 oom_score_adj 包括 Google 自己的手机 Nexus 6,压根就没见过官网提到的 oom_adj_score

  在 Kernel 层使用。从 Android 7 开始,其取值范围发现了变化,具体参考新的 ProcessList.java 文件。(来自 AOSP master 分支)

1
2
3
4
5
6
7
// These are the various interesting memory levels that we will give to
// the OOM killer. Note that the OOM killer only supports 6 slots, so we
// can't give it a different value for every possible kind of process.
private final int[] mOomAdj = new int[] {
FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,
PERCEPTIBLE_LOW_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_LMK_FIRST_ADJ
};

  该类中还定义了很多值(值越大,重要性越低,进程越容易被杀清理):

常量定义 常量取值 含义
INVALID_ADJ -10000 对于任何主要或将要的 adj 字段的未初始化值
NATIVE_ADJ -1000 native 进程的特殊值(不被系统管理)系统不为其指定 oom adj 值
SYSTEM_ADJ -900 系统进程
PERSISTENT_PROC_ADJ -800 系统 persistent 进程,比如 telephony。当然该进程不会被清理,不过若被清理的话,也不会产生完全致命错误
PERSISTENT_SERVICE_ADJ -700 绑定到系统或 persistent 进程的进程
FOREGROUND_APP_ADJ 0 前台进程即正在使用的应用
PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ 50 最近刚刚被移至 FGS 的进程。系统在一段时间内,会继续像前台应用一样对待该应用
VISIBLE_APP_ADJ 100 可见进程(详见下文)
PERCEPTIBLE_APP_ADJ 200 可被感知进程(详见下文),通常系统会避免清理该进程
PERCEPTIBLE_LOW_APP_ADJ 250 被系统或其他应用绑定的进程。该进程比 Service 重要。清理该进程并不会马上引起用户的注意
BACKUP_APP_ADJ 300 持有备份操作的进程。虽然清理该进程并不会产生致命错误,但这并不是一个好主意
HEAVY_WEIGHT_APP_ADJ 400 后台的重量级进程,应该尽管避免清理该进程。在 system/rootdir/init.rc 中进行设置
SERVICE_ADJ 500 服务进程。清理后并不会对用户产生明显影响
HOME_APP_ADJ 600 Home 进程。尽量避免清理该进程,因为用户会经常返回到 Launcher
PREVIOUS_APP_ADJ 700 前一次使用的应用。用户很可能会再次返回该应用
SERVICE_B_ADJ 800 SERVICE_ADJ 中的 B List。较久的、被使用的可能性更小的 Service。重要度不如 SERVICE_ADJ 中的 A List
CACHED_APP_MIN_ADJ 900 不可见进程的 adj 最小值
CACHED_APP_LMK_FIRST_ADJ 950 可以允许被首先清理的 oom_adj 值。并不等同于 CACHED_APP_MAX_ADJ 除非该进程 oom_score_adj 已被赋值为 CACHED_APP_MAX_ADJ
CACHED_APP_MAX_ADJ 999 不可见进程的 adj 最大值
UNKNOWN_ADJ 1001 一般指将要被缓存但是又无法明确赋于其确定值的进程

  当触发 LowMemoryKiller 机制时,可根据日志中进程的 adj 值,具体分析进程是在什么状态下被杀死的。

  另外,oom_adjoom_score_adj 的换算方法定义在 Linux Driver 中。

  以 android-10.0.0_r0.30 为例,lowmemorykiller.c 中涉及的代码如下:

1
2
3
4
5
6
7
static short lowmem_oom_adj_to_oom_score_adj(short oom_adj)
{
if (oom_adj == OOM_ADJUST_MAX)
return OOM_SCORE_ADJ_MAX;
else
return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
}

Kill 策略

  lmkd 支持基于 vmpressure 事件或 PSI Monitor 的 Kill 策略,依据其重要性以及其它指标,例如交换利用率(swap utilization)等。对于低内存设备与高性能设备之间的 Kill 策略是不同的:

  • 低内存设备上(Android 通常将内存不足 512MB 的设备作为低内存设备),系统应该能承受较高内存压力,并保证正常运行。毕竟机器本身内存就小,应用经常处于内存不足的情况是很正常的。当然,对于低内存设备而言,目前应该已经非常罕见了,大家了解一下就好。
  • 在高性能设备上,就应该将内存压力视为异常情况。并在影响整体性能之前对其加以修复。

    可以使用 ro.config.low_ram 属性来设置当前设备是否是低内存设备(详见 Low RAM Configuration)。

    lmkd 还支持一种传统模式,在该模式中,它使用与内核 lowmemorykiller 驱动程序相同的策略(即依据可用内存和文件缓存阈值)来判断是否需要进行 Kill 操作。 要启用传统模式,请将 ro.lmk.use_minfree_levels 属性设置为 true

开启 lmkd

  在 Android 9 及更高版本中,如果未检测到内核中的 lowmemorykiller 驱动程序,则会使用 lmkd。 因为 lmkd 需要内核支持内存 cgroup ,而且必须使用以下设置来编译内核:

1
2
3
CONFIG_ANDROID_LOW_MEMORY_KILLER=n
CONFIG_MEMCG=y
CONFIG_MEMCG_SWAP=y

Pressure stall information (PSI)

  Android 10 及更高版本还支持一种新的 lmkd 模式,该模式使用 Pressure stall information(PSI) Monitor 进行内存压力检测。 上游内核(回溯到 4.9 和 4.14 内核)中的 PSI 补丁集可测量由于内存不足导致任务延迟的时间。 由于这些延迟直接影响用户体验,因此它们是确定内存压力严重性的便捷指标。 上游内核还包括 PSI Monitor ,该 Monitor 允许某些特权进程(例如 lmkd)为这些延迟指定阈值,并在超过阈值时收到来自内核的相关事件。

PSI monitors VS vmpressure 信号

  由于 vmpressure 信号(由内核生成,用于检测内存压力并由 lmkd 使用)通常包含许多误报,因此 lmkd 必须执行一些过滤操作,以确定是否真的处于内存不足状态。 这会导致不必要的 lmkd 唤醒并使用其它计算资源。 使用 PSI Monitor 则可以更准确地检测内存压力,并最大程度地减少过滤开销。

开启 PSI Monitor

  要使用 PSI Monitor 而不是 vmpressure 事件,请配置 ro.lmk.use_psi 属性。 默认值为 true,这样就可以将 PSI 设置为 lmkd 的默认内存压力检测机制。 因为 PSI Monitor 需要内核支持,所以内核必须包含 PSI backport 补丁集,并且使用以下配置来编译内核:

1
CONFIG_PSI=y

配置 lmkd

可以通过如下属性对 lmkd 进行配置:

Property Use Default
ro.config.low_ram Choose between low-memory versus high-performance device. false
ro.lmk.use_psi Use PSI monitors (instead of vmpressure events). true
ro.lmk.use_minfree_levels Use free memory and file cache thresholds for making process kill decisions (that is, match the functionality of the in-kernel lowmemorykiller driver. false
ro.lmk.low The minimum oom_adj score for processes eligible to be killed at low vmpressure level. 1001 (disabled)
ro.lmk.medium The minimum oom_adj score for processes eligible to be killed at medium vmpressure level. 800 (cached or non-essential services)
ro.lmk.critical The minimum oom_adj score for processes eligible to be killed at critical vmpressure level. 0 (any process)
ro.lmk.critical_upgrade Enable upgrade to critical level. false
ro.lmk.upgrade_pressure The maximum mem_pressure at which the level is upgraded because the system is swapping too much. 100 (disabled)
ro.lmk.downgrade_pressure The minimum mem_pressure at which a vmpressure event is ignored because enough free memory is still available. 100 (disabled)
ro.lmk.kill_heaviest_task Kill heaviest eligible task (best decision) versus any eligible task (fast decision). true
ro.lmk.kill_timeout_ms Duration in ms after a kill when no additional kill will be done. 0 (disabled)
ro.lmk.debug Enable lmkd debug logs. false

说明:
mem_pressure = RAM usage/RAM_and_swap 单位是 %.

low-memory killer (LMK) Kill 顺序

  系统会按照下图从上往下的顺序对进程进行 Kill 操作。当设备可用内存低于 LMK 阈值时,LMK 便会根据进程的优先级,逐级 Kill 进程并释放其占用的资源。

lmk process order

  系统会根据一个叫做 oom_adj_score(实际情况也有叫 oom_score_adj 的)“内存不足”评分,来确定要优先 Kill 哪些进程。该评分越高,代表要优先被 Kill。从上图我们可以看到“后台应用”会被优先 Kill,而系统进程是最后才被 Kill 的。每个 Java 进程都有一个相关联的 ProcessRecord 对象,其成员变量 curAdj 就表示该进程当前状态下的优先级:

1
int curAdj; // Current OOM adjustment for this process

  我们从头来说明一下上表中的优先级:

  • 后台应用(Background apps)
  • 上次使用的应用(Previous app)
  • Home 应用(Home App)(也就是 Launcher)
  • 服务(Services)
  • 可被感知应用(Perceptible apps)
  • 前台应用(Foreground apps)
  • 常驻进程(Persistent)
  • 系统进程(System)
  • Native 进程(Native)

后台应用(Background apps)

  先前已经运行且当前未激活的应用程序。 LMK 将首先杀死具有最高 oom_adj_score(也可能叫 oom_score_adj) 的后台应用程序。

上次使用的应用(Previous app)

  最近使用的后台应用程序。 与“后台应用”相比,“上次使用的应用”具有更高的优先级(较低的分数),因为与“后台应用”相比,用户更有可能切换到该应用。

Home 应用(Home App)

  就是 Launcher 应用。Kill 该应用会导致壁纸消失。
  FYI:虽然官方文档是这么写的,但是在实际测试中,结果确和官方说的相反。

  测试机型:rk3288 主机板 - Android 7.1.2 已 Root
  直接使用 kill 命令,或者 am force-stop 命令停止第三方 Launcher运行后,Launcher 并不会被自动启动,看起来的效果就是 Launcher 上的图标都消失了,仅留下了壁纸。刚好和官方文档说的相反。

  但是,使用 kill 命令结束 系统 Launcher 进程的话,Launcher 会被自动启动,并且壁纸也不会消失。使用 am force-stop 命令时,效果和之前的第三方 Launcher一样,Launcher 不会被自动重启,仅图标消失了,壁纸依然在。

  测试机型:Nexus6 真机 - Android 7.1.1 已 Root
  无论使用 kill 命令还是 am force-stop 命令,结束系统 Launcher 后,Launcher 都会被自动重启,重启后一切正常,图标和壁纸都没有问题。

服务(Services)

  应用程序启动的服务。尽管服务与应用程序的可见性没有直接关系,不过在服务中很有可能会执行一些与用户有关系的操作。例如,在后台播放音乐;从网络上传或下载数据等。因此,服务的重要程序还是很高的。除非内存不足以维持前台应用和可被感知应用的运行,否则系统会让服务保持运行状态。

  PS:系统会优先 Kill Home 应用,之后才会 Kill 服务。就是我之前没有想到的。

可被感知应用(Perceptible apps)

  以某种方式可以被用户感知的非前台应用。虽然整个应用并不在前台运行,但是依然可以被用户察觉该应用正在运行。例如,在搜索过程中显示的一个小 UI;播放器应用正在播放音乐。

前台应用(Foreground apps)

  当前正在使用的应用。 Kill 前台应用时,看起来就像是应用闪退了一样。此现象可能会让用户感觉到有可能是设备出问题了。

常驻进程(Persistent)

  设备的核心服务。例如电话和 wifi。

系统进程(System)

  系统进程。 如果这些进程被 Kill 的话,设备可能会重启。

Native 进程(Native)

  系统使用的非常低层的进程。(例如:kswapd)。

注意:设备制造商有可能会修改 LMK 的行为。

Android 进程分类及状态

  刚刚我们了解了 LMK Kill 应用的顺序。接下来我们有必要了解下 Android 进程的分类及各种状态。

进程分类

  根据官方文档进程和线程概览进程和应用生命周期内容可知,Android 的进程分为以下四类(按重要度由高到低排序):

前台进程(foreground process)

满足以下任一条件的进程,将被视为前台进程:

  • 当前正在与用户交互的 Activity(即 onResume()方法已被调用)
  • 正在执行的 BroadcastReceiver(即 BroadcastReceiver.onReceive() 方法正在被执行)
  • Service 正处于以下生命周期之一:Service.onCreate()Service.onStart()Service.onDestroy()

      通常,系统中只有少数进程会属于前台进程,并且只有在内存过低时以至于无法让其继续运行的情况下,这些进程才会被 Kill。 一般来说,此时设备已达到内存分页状态,清理前台进程是为了让用户继续操作手机时,设置能处于响应状态,不至于无响应。

可见进程(visible process)

应用不完全可见,但是用户能够察觉到有进程在运行。结束可见进程会对用户体验造成负面影响。满足以下任一条件的进程,将被视为可见进程:

  • Activity 虽然可见,但是并不处于前台状态(即 onPause() 已被调用)。例如,当前 Activity 被另一个 Activity 不完全覆盖时(即显示一个半透明的 Activity),此时之前的 Activity 会处于 onPause() 状态。

  • 通过 Service.startForeground() 运行的前台 Service。因为前台 Service 对于用户而言是可以被察觉到的或者是有必要显示给用户看的。

      与前台进程相比,在系统可控范围内,可同时运行的可见进程的数量是不受限制的。因为这些进程对于用户而言是非常重要的。除非影响到了前台进程的进行,否则系统会让所有前台进程保持运行状态而不会清理它们。

服务进程(service process)

  通过 startServie() 方法启动 Service 的进程。虽然这些进程对用户而言并不是直接可见的,但是它们正在执行一些用户可能比较关心的事件。例如,在后台上传或下载数据。除非没有足够的内存来运行所有的前台进程和可见进程,否则系统将始终保持服务进程的运行。

  如果一个 Service 已经运行了很长时间(例如,超过30分钟),那么它的优化级可能会被降低,以使其所在的进程可以被添加到 LRU 列表中(下文会讲到)。 这有助于避免在长时间运行的 Service 中若存在内存泄漏或其他原因导致占用大量内存的情况,倘若出现这种情况的话,系统就无法有效的利用缓存进程了。

缓存进程(cached process)

  缓存进程是当前不需要的进程,因此系统可以根据需要随意清理它们。在正常运行的系统中,这些是内存管理唯一涉及到的进程:一个运行良好的系统将始终具有多个可用的缓存进程(这样可以在应用程序之间进行有效的切换),并根据需要定期清理最早使用的进程。只有在非常关键的情况下,系统才会达到清理所有缓存进程的地步,此时也必须开始清理服务进程。

  这些进程通常持有一个或多个当前对用户不可见的 Activity 实例(即 onStop() 方法已执行完成)。如果已经正确实现了 Activity 生命周期中的方法的话(请查看 Activity 以了解更多详细内容 ),那么当系统清理此类进程时,并不会影响用户再次返回该应用程序时的体验:因为在一个新的进程重新创建该 Activity 时,它可以恢复到以前被保存时的状态。

  这些进程被保存在一个伪 LRU(pseudo-LRU)列表中,当需要回收内存时,首先清理的是列表中的最后一个进程。该列表中进程的确切排序策略是平台实现的。通常情况下,系统会尽可能的保留那些重要度高的进程。(例如,Home 应用(即 Launcher);最后使用的应用等)。另外,还有一些其它可用于清理进程的策略:例如,对允许运行的进程数量进行硬性限制;限制应用被缓存的时间等。

进程状态

PS:这部分不太好翻译,因为有些内容我不是很理解。索性就不翻译了。

State Meaning Details
SERVICE SERVICE_RESTARTING Apps that run in the background for app-related reasons. SERVICE SERVICE_RESTARTING are the most common problems apps have when they run in the background too much. Use %duration \ pss or %duration* as a “badness” metric. Ideally, these apps shouldn’t be running at all.
IMPORTANT_FOREGROUND RECEIVER Apps running in the background (not directly interacting with the user). These add memory load to the system. Use the (%duration \ pss) “badness” value to order these processes. However, many of these apps run for good reasons. The size of pss* is an important part of their memory load.
PERSISTENT Persistent system process. Track pss to watch for these processes getting too large.
TOP The process the user is currently interacting with. pss is the important metric here, showing how much memory load the app creates while in use.
HOME CACHED_EMPTY The processes the system is keeping around in case they are needed again. These processes can be freely killed at any time and recreated if needed. The memory state (normal, moderate, low, critical) is computed based on how many of these processes the system is running. The key metric for these processes is the pss. In this state, these processes should decrease their memory footprint as much as possible to allow for the maximum total number of processes to be kept around. In this state, a well behaved app generally has a significantly smaller pss footprint than it does in the TOP state.
CACHED_ACTIVITY CACHED_ACTIVITY_CLIENT When compared with TOP, these show how well an app releases memory into the background. Excluding CACHED_EMPTY state makes this data better, because it removes situations when the process has started for some reasons besides interacting with the user. This eliminates dealing with the UI overhead CACHED_EMPTY gets when user-related activities.

自定义 LMK 清理规则

  这部分内容由于涉及修改 ROM,在这里就不多讲了。不过根据亲自实验的结果,需要特别提醒大家,如果修改了 LMK 相关的参数,可能会影响系统的稳定性,因此要谨慎。尤其是对于内存较小的设备。

  感兴趣的可以参考以下链接:

参考文献

坚持原创及高品质技术分享,您的支持将鼓励我继续创作!