进程的启动分为冷启动和热启动,当用户退出进程后,Android系统不会立即将此进程回收,而是将其放到后台运行,下次再启动这个程序的时候,直接将这个放在后台的进程拉起来使用,加快启动速度,这种启动方式称为热启动。而冷启动则是重新为这个程序分配进程。
那么问题来了,当启动的程序较多,然后又退出了,后台就会留下很多这种空的进程,占据了大量的内存空间。Android当内存剩余的空间满足一定的条件时,会对后台的进程进行查杀,以保证内存是可用的,这就是Android中LMK(LowMemoryKiller机制)
在查杀的过程中,LMK是有一定的侧重点的,按照一定的等级进行查杀,先查杀等级最低的,以此类推。
1.前台进程
- 和用户正在进行交互的
activity
所属的应用的进程 - 拥有某个
Service
,后台绑定到用户正在交互的Activity
- 拥有正在”前台”运行的
Service
(服务已经调用startForeground()
) - 正在执行一个生命周期回调的
Service
(onCreate()
、onStart()
、onDestroy()
) - 正在执行
onReceive()
方法的BroadCastReceiver
Android系统只有在万不得已的情况下才会回收此进程(一般回收此种进程的概率并不大)
2.可见进程
- 绑定到可见前台的Service
- 托管不在前台,但仍对用户可见的Activity(已调用其
onPause()
方法)
3.服务进程
- 正在执行
startService()
方法开启服务的进程
4.后台进程
后台会存在很多这种进程,当内存不足时,系统会终止他们,这些后台进程会被系统保存在LRU列表中,以保证用户最先访问的进程最后一个被终止。
- activity已经调用
onStop()
方法,表示已经不可见的进程
5.空进程
- 此种进程就是某一个进程退出后,缓存在后台的进程,只是当做缓存进程,加速下一次的启动,可能随时被回收
LMK(LowMemoryKiller)原理
Android会为每个进程维护一个adj(在Android 6之前包括6称为oom_adj
,在android 7开始为oom_score_adj
)的值,在手机文件中存在着两个文件(不同类型的手机可能以下两个文件中的数值是不一样的)
/sys/module/lowmemorykiller/parameters/minfree
,这个文件里的内容是一串以逗号分隔开的数组,每一项代表一个内存级别
/sys/module/lowmemorykiller/parameters/adj
也是一个数组,每一项代表着oom_adj的值
我到目前查遍了整个文件系统也没在手机里找到这两个文件,不知道是什么原因,下面的案例数据我是在参考链接里截取的
这两个文件相互配合,就能确定杀掉哪些进程
两个文件相互对应着看,当可用内存低于80640个内存页的时候,会杀掉adj的值大于906的进程,当可用内存小于55296个内存页的时候,会杀掉adj大于900的进程,以此类推。
adj是在动态计算的,当app的状态以及四大组件的生命周期在改变时,都会改变adj的值,每个进程的adj的值都存储在/proc/pid/oom_adj
下,高版本的内核已经不再使用oom_adj
这个文件,而是使用oom_adj_score
这个文件。
其中进程的状态定义在ActivityManager.java
中,这些状态的作用就是帮助AMS计算adj(其实四大组件的变化就会对应到这里的进程状态)
常量定义 | 常量取值 | 含义 |
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 | 不存在的进程 |
在Android 6以及之前,oom_adj的值取值范围为[-17,16],Android 7开始,adj的取值变为了[-1000, 1000]。
将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 | 一般指将要会缓存进程,无法获取确定值 |
看一下adj
这个值是如何随着四大组件进行变化的:
以今日头条app进行举例,目前一个app可能对应着多个进程,当我们启动头条app后,使用头条app的包名即可查找到所有头条启动的进程
第一个进程的pid
为7585,此时处于activity
可见的状态,查看一下adj
的值
当app处于不可见(activity不可见)下,adj的值为
有了上面的概念,这下我们来总结一下LowMemoryKiller
的工作流程
当操作app的时候,例如从前台切换到后台(导致四大组件的生命周期发生变化),此时AMS会重新计算adj的值,在native
层的LMKD(LowMemoryKiller Daemo,LMK的守护线程)内部维护着一张表(数组),这个数组内部每一个元素又是一个双向链表,每个链表中的内容为相同adj的进程信息,当AMS重新计算完adj的值后,会通过socket通信的方式将更新的oom_adj的值传递给LMKD,LMKD更新维护的这张表。当内存达到上面文件中配置的要求时,会从链表中选出一些去干掉进程,回收内存空间。下面还有一张图,同样是描述的LowMemoryKiller工作机制
上面提到了LMKD守护线程,这个线程是由init进程(pid = 1)的进程通过分析/etc/init/lmkd.rc
生成的,并且在启动后,还会在/dev/socket
下创建一个名为lmkd的套接字,提供给framework层和native层通信。
源码分析
先梳理一下流程:当四大组件的状态发生改变时,AMS(ActivitymanagerService)直接或间接调用ProcessList.java
中的方法与native层的LMKD进行通信,LMKD维护自身的进程数组(数组中的每个节点又是一组相同adj的进程),最后kernel层负责对进程查杀
在ProcessList.java
中,定义了七种命令
并且在LMKD守护进程中,(lmkd.h)也有着与之相同的命令
native层的lmkd与framework层的AMS就是通过这些命令进行交互的,常用的命令其实只有前三个
- LMK_TARGET
- LMK_PROCPRIO
- LMK_PROCREMOVE
LMK_PROCPRIO
这个命令就是负责更新某个进程的adj值,在AMS中,如果感知到某个进程的四大组件状态发生改变,会调用OomAdjuster.updateOomAdjLocked
方法,而OomAdjuster.updateOomAdjLocked
又会调用OomAdjuster.applyOomAdjLocked
,在applyOomAdjLocked
中最终调用了ProcessList.setOomAdj
方法。
在AMS中可以找到很多地方调用了OomAdjuster.updateOomAdjLocked
方法,这些调用者基本上都是四大组件改变时的函数。
主要就是看ProcessList.setOomAdj
这个方法,此方法是负责与native层的socket通信
writeLmkd
LmkdConnection.isConnected
LmkdConnection.connect
会在ProcessList
中调用connect打开连接,然后再setOomAdj
向native层中的LMKD发送更新adj的消息。
转入到LMKD中(lmkd.cpp)
在main中会调用mainloop
,mainloop
中有着一大串的逻辑,简化完就是调用epoll_wait
等待framework
层发送的消息,接收到消息后会调用在init
中定义好的回调方法ctrl_connect_handler
在ctrl_connect_handler
中调用ctrl_data_handler
,接下来ctrl_command_handler
。
ctrl_command_handler
里面就包含了在ProcessList
中定义的6个命令,这六个命令会进行不同的逻辑处理
我们此次分析的是更新进程adj
逻辑,即LMK_PROCPRIO
指令,所以进入到cmd_procprio
我们对adj更新的分析就到这啦,在深入一层的kernel就不分析了,主要就是对满足条件的进程进行kill
LMK_TARGET
当系统启动时,需要更新minfree
和adj范围数组,这两个数组在ProcessList.java
中有两个范围值
更新就是通过ProcessList.updateOomLevels()
更新adj范围值和minfree
的,主要就是在系统启动的时候更新,调用是在ProcessList
的构造函数中
而updateOomLevels
则是根据当前屏幕的分辨率宽高等进行更新的
转到native层LMKD,由于逻辑与处理函数同上面的更新adj方法,所以只拿出来ctrl_command_handler
进行分析即可,通过命令选择,最终执行了cmd_target
方法,这个方法就只看两部分就可以了,重要逻辑就是更新minfree和adj范围值
LMK_PROCREMOVE
该命令的主要作用就是将当前进程从LMKD策略中移除,也就是将进程节点信息从lmkd.cpp
中的数组中的链表移除,不受lmkd
策略的控制。
当AMS调用handleAppDiedLocked
时,会调用cleanUpApplicationRecordLocked
,其中AMS.cleanUpApplicationRecordLocked
调用ProcessList.java
中的
ProcessList.removeProcessNameLocked
remove
在native层LMKD中,进行分发任务,最终到cmd_procremove
中
我想大家应该明白这个流程了,不用介绍太多大家也会懂得
关于杀进程的逻辑
上一步中有一个标志use_inkernel_interface
,当其值为1的时候,是调用内核的逻辑去杀进程的,大概的流程就是系统内部存在一个进程kswapd,内部维护了一张shrinker表,当内存不足的时候或者达到了一定的界限的时候,就会触发绑定在shrinker表的回调函数,而回调函数基本上就是杀进程的逻辑,得到当前内存值,然后获取当前内存对应的adj的值,遍历进程表,将遍历得到的进程adj,把所有大于剩余内存对应的adj的进程全部干掉(发送SIGKILL信号)。
关于LMK策略的一些思考
1.有没有杀不死的进程
从native进程的角度来说,是存在杀不死的进程的,native进程的最小值为-1000,如果将进程的adj值修改为-1000,从一定的角度来说是杀不死的
2.LMKD进程会不会被LMK策略杀死
如果native进程不会被杀死,那么LMKD进程也是不会被杀死的,看一下LMKD的adj就明白怎么回事了
3.市面上的app可不可以将自己的adj值提升到-1000,以保活
当然不可以,市面上的app无法root手机,也就是获取不到手机的root权限,也就无法修改adj,修改/proc/pid/oom_score_adj
是需要root权限的,如果,说的是如果,如果被修改了adj值,那么下次重新启动也是会恢复的,除非不关机
系统软件如桌面都会进程一些保活的机制,就是降低自己的adj值,查看一下桌面的adj值
可见,桌面在后台时adj值都这么低,被杀的几率当然很小
参考链接
- Android进程系列第八篇---LowmemoryKiller机制分析(下) - 简书
- https://www.jianshu.com/p/221f4a246b45(特别推荐)
- https://www.jianshu.com/p/980b6ce4e051(特别推荐)
- https://www.jianshu.com/p/7bd3d0ee8a56(特别推荐)
- (特别推荐)
- http://gityuan.com/2016/09/17/android-lowmemorykiller/(特别推荐