读书笔记,如需转载,请注明做者:Yuloran (t.cn/EGU6c76)java
笔者在以前的文章《分析并优化 Android 应用内存占用》中提到,为了不 Cached Pages 太少时致使设备卡顿、死机、重启等状况,Android 引入了 LowMemoryKiller(源自 Linux OOM Killer) 机制,提早回收优先级比较低的进程所占的资源,以保证一个较好的用户体验。进程优先级列表由 SystemServer 进程维护:linux
其中 Cached 进程列表,使用的是 LRU 算法。android
每一个 Java 进程都有一个相关联的 ProcessRecord 对象,其成员变量 curAdj 就表示该进程当前状态下的优先级:git
int curAdj; // Current OOM adjustment for this process
复制代码
当设备可用内存低于 LMK 阈值时,LMK 便会根据进程的优先级,逐级杀死进程并释放其占用的资源。算法
建议先阅读笔者前四篇博文,以对 Android 内存相关有一个比较全面而深刻的了解:shell
本篇博文为 Android 内存系列的最后一篇。api
本小节摘自 Google Android Developers 《进程和线程》,LMK 杀死进程时,将听从 空进程 -> 后台进程 -> 服务进程 -> 可见进程 -> 前台进程
的顺序:数组
用户当前操做所必需的进程。若是一个进程知足如下任一条件,即视为前台进程:缓存
一般,在任意给定时间前台进程都为数很少。只有在内存不足以支持它们同时继续运行这一万不得已的状况下,系统才会终止它们。bash
没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 若是一个进程知足如下任一条件,即视为可见进程:
可见进程被视为是极其重要的进程,除非为了维持全部前台进程同时运行而必须终止,不然系统不会终止这些进程。
正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。
尽管服务进程与用户所见内容没有直接关联,可是它们一般在执行一些用户关心的操做(例如,在后台播放音乐或从网络下载数据)。所以,除非内存不足以维持全部前台进程和可见进程同时运行,不然系统会让服务进程保持运行状态。
包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。
这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 一般会有不少后台进程在运行,所以它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。
不含任何活动应用组件的进程。
保留这种进程的的惟一目的是用做缓存,以缩短下次在其中运行组件所需的启动时间。 为使整体系统资源在进程缓存和底层内核缓存之间保持平衡,系统每每会终止这些进程。
Andorid 的 Low Memory Killer 是在标准的 Linux Kernel 的 OOM Killer 基础上修改而来的一种内存管理机制。当系统内存不足时,杀死不重要的进程以释放其内存。LMK 的关键参数有 3 个:
取值范围定义:
-> ProcessList(AOSP, master 分支)
// 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,
BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ
};
复制代码
以上仅是 LMK 杀死进程时使用的 adj,实际上该类中定义了更多的 adj:
常量定义 | 常量取值 | 含义 |
---|---|---|
NATIVE_ADJ | -1000 | native进程(不被系统管理) |
SYSTEM_ADJ | -900 | 系统进程 |
PERSISTENT_PROC_ADJ | -800 | 系统persistent进程,好比telephony |
PERSISTENT_SERVICE_ADJ | -700 | 关联着系统或persistent进程 |
FOREGROUND_APP_ADJ | 0 | 前台进程 |
VISIBLE_APP_ADJ | 100 | 可见进程 |
PERCEPTIBLE_APP_ADJ | 200 | 可感知进程,好比后台音乐播放 |
BACKUP_APP_ADJ | 300 | 备份进程 |
HEAVY_WEIGHT_APP_ADJ | 400 | 后台的重量级进程,system/rootdir/init.rc文件中设置 |
SERVICE_ADJ | 500 | 服务进程 |
HOME_APP_ADJ | 600 | Home进程 |
PREVIOUS_APP_ADJ | 700 | 上一个App的进程 |
SERVICE_B_ADJ | 800 | B List中的Service(较老的、使用可能性更小) |
CACHED_APP_MIN_ADJ | 900 | 不可见进程的adj最小值 |
CACHED_APP_MAX_ADJ | 906 | 不可见进程的adj最大值 |
UNKNOWN_ADJ | 1001 | 通常指将要会缓存进程,没法获取肯定值 |
以上常量在 Android 6.0(API23)及以前版本的取值范围为 [-17, 16]:ProcessList(AOSP,marshmallow-release 分支)
规律:取值越大,重要性越低,进程越容易被杀死。
当触发 LowMemoryKiller 机制时,可根据日志中进程的 adj 值,具体分析进程是在什么状态下被杀死的。
取值范围定义:
-> ProcessList(AOSP, master 分支)
// 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
};
复制代码
数组初始化或更新的方法:
private void updateOomLevels(int displayWidth, int displayHeight, boolean write) {
float scaleMem = ((float)(mTotalMemMb-350))/(700-350);
int minSize = 480*800; // 384000
int maxSize = 1280*800; // 1024000 230400 870400 .264
float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize);
float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp;
...省略
final boolean is64bit = Build.SUPPORTED_64_BIT_ABIS.length > 0;
for (int i=0; i<mOomAdj.length; i++) {
int low = mOomMinFreeLow[i];
int high = mOomMinFreeHigh[i];
if (is64bit) {
// Increase the high min-free levels for cached processes for 64-bit
if (i == 4) high = (high*3)/2;
else if (i == 5) high = (high*7)/4;
}
mOomMinFree[i] = (int)(low + ((high-low)*scale));
}
...省略
if (write) {
ByteBuffer buf = ByteBuffer.allocate(4 * (2*mOomAdj.length + 1));
buf.putInt(LMK_TARGET);
for (int i=0; i<mOomAdj.length; i++) {
// 除以了 PAGE_SIZE,因此 minfree 中的单位为页,及 4KB
buf.putInt((mOomMinFree[i]*1024)/PAGE_SIZE);
buf.putInt(mOomAdj[i]);
}
// 向 lmkd 进程发送 LMK_TARGET 命令,
// 将 oom_adj 阈值写入 "/sys/module/lowmemorykiller/parameters/minfree"
// 将 oom_adj 写入 "/sys/module/lowmemorykiller/parameters/adj"
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
}
复制代码
在 ActivityManagerService 调用 updateConfiguration() 的过程当中会调用该方法,根据设备的分辨率初始化或更新阈值的大小。
取值范围定义在 Linux Kernel 中:
-> oom.h
#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 到 oom_score_adj 的换算方法定义在 Linux Driver 中:
static int lowmem_oom_adj_to_oom_score_adj(int oom_adj) {
if (oom_adj == OOM_ADJUST_MAX)
return OOM_SCORE_ADJ_MAX;
else
return (oom_adj * OOM_SCORE_ADJ_MAX) / -OOM_DISABLE;
}
复制代码
简述 LMK 的工做流程
lmkd 是由 init进程经过解析 init.rc 文件来启动的守护进程。lmkd 会建立名为 lmkd 的 socket,节点位于 /dev/socket/lmkd,该 socket 用于跟上层 framework 交互:
service lmkd /system/bin/lmkd
class core
group root readproc
critical
socket lmkd seqpacket 0660 system system
writepid /dev/cpuset/system-background/tasks
复制代码
lmkd 启动后,便会进入循环等待状态,接受来自 ProcessList 的三个命令:
命令 | 功能 | 方法 |
---|---|---|
LMK_TARGET | 初始化 oom_adj | ProcessList::setOomAdj() |
LMK_PROCPRIO | 更新 oom_adj | ProcessList::updateOomLevels() |
LMK_PROCREMOVE | 移除进程(暂时无用) | ProcessList::remove() |
在 ActivityManagerService 调用 updateConfiguration() 的过程当中会调用 ProcessList::updateOomLevels() 方法,根据设备的分辨率调整阈值的大小,经过 LMK_TARGET 命令,通知 lmkd(low memory killer deamon)分别向 /sys/module/lowmemorykiller/parameters 目录下的 minfree 和 adj 节点写入相应信息:
在 ActivityManagerService 调用 applyOomAdjLocked() 的过程当中会调用 ProcessList::setOomAdj() 方法,经过 LMK_PROCPRIO 命令,通知 lmkd 向 /proc/进程号/oom_score_adj 写入 oomadj:
D:\Android\projects\wanandroid_java>adb shell ps | findstr wanandroid u0_a71 6461 1285 1177696 84024 SyS_epoll_ b131e424 S com.yuloran.wanandroid_java D:\Android\projects\wanandroid_java>adb shell ls /proc/6461/ ...省略 oom_adj oom_score oom_score_adj ... 复制代码
ActivityManagerService 会根据当前应用进程托管组件(即四大组件)生命周期的变化,及时的调用 applyOomAdjLocked(),更新进程状态及该状态对应的 oom_adj。
进程状态表:
-> ActivityManager.java(AOSP,branch: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 | 3 | 托管一个前台 Service 的进程 |
PROCESS_STATE_BOUND_FOREGROUND_SERVICE | 4 | 托管一个由系统绑定的前台 Service 的进程 |
PROCESS_STATE_IMPORTANT_FOREGROUND | 5 | 对用户很重要的进程,用户可感知其存在 |
PROCESS_STATE_IMPORTANT_BACKGROUND | 6 | 对用户很重要的进程,用户不可感知其存在 |
PROCESS_STATE_TRANSIENT_BACKGROUND | 7 | Process is in the background transient so we will try to keep running. |
PROCESS_STATE_BACKUP | 8 | 后台进程,正在运行backup/restore操做 |
PROCESS_STATE_SERVICE | 9 | 后台进程,且正在运行service |
PROCESS_STATE_RECEIVER | 10 | 后台进程,且正在运行receiver |
PROCESS_STATE_TOP_SLEEPING | 11 | 与 PROCESS_STATE_TOP 同样,但此时设备正处于休眠状态 |
PROCESS_STATE_HEAVY_WEIGHT | 12 | 后台进程,但没法执行restore,所以尽可能避免kill该进程 |
PROCESS_STATE_HOME | 13 | 后台进程,且拥有 home Activity |
PROCESS_STATE_LAST_ACTIVITY | 14 | 后台进程,且拥有上一次显示的 Activity |
PROCESS_STATE_CACHED_ACTIVITY | 15 | 进程处于 cached 状态,且内含 Activity |
PROCESS_STATE_CACHED_ACTIVITY_CLIENT | 16 | 进程处于 cached 状态,且为另外一个 cached 进程(内含 Activity)的 client 进程 |
PROCESS_STATE_CACHED_RECENT | 17 | 进程处于 cached 状态,且内含与当前最近任务相对应的 Activity |
PROCESS_STATE_CACHED_EMPTY | 18 | 进程处于 cached 状态,且为空进程 |
PROCESS_STATE_NONEXISTENT | 19 | 不存在的进程 |
同一个进程,在不一样状态下,其 oom_adj 是不同的。
在 ActivityManagerService 调用 updateOomAdjLocked() 时,会判断进程是否须要被杀死,如果,则调用 ProceeRecord::kill() 方法杀死该进程:
注: 目前 LMK_PROCREMOVE 命令暂时无用,即未执行有意义的代码。
Android 内存系列至此,所有完结😁。若有错误,还望不吝赐教🤝。