【性能】OOM原理解析:LowMemoryKiller原理

时间:2024-04-06 16:22:54

1 概述

Android的设计理念之一,便是应用程序退出,但进程还会继续存在系统以便再次启动时提高响应时间. 这样的设计会带来一个问题, 每个进程都有自己独立的内存地址空间,随着应用打开数量的增多,系统已使用的内存越来越大,就很有可能导致系统内存不足, 那么需要一个能管理所有进程,根据一定策略来释放进程的策略,这便有了lmk,全称为LowMemoryKiller(低内存杀手),lmkd来决定什么时间杀掉什么进程。1

Android基于Linux的系统,其实Linux有类似的内存管理策略——OOM killer,全称(Out Of Memory Killer), OOM的策略更多的是用于分配内存不足时触发,将得分最高的进程杀掉。而lmk则会每隔一段时间检查一次,当系统剩余可用内存较低时,便会触发杀进程的策略,根据不同的剩余内存档位来来选择杀不同优先级的进程,而不是等到OOM时再来杀进程,真正OOM时系统可能已经处于异常状态,系统更希望的是未雨绸缪,在内存很低时来杀掉一些优先级较低的进程来保障后续操作的顺利进行。

1.1 LMK的基本功能

  • 进程优先级定义:只有定义了优先级,才能决定先杀死谁,后杀死谁2
  • 进程优先级的动态管理:一个进程的优先级不应该是固定不变的,需要根据其变动而动态变化,比如前台进程切换到后台进程优先级就应该降低
  • 进程杀死的时机:什么时候杀死进程
  • 如何杀死

以上几个都是LMK的基本功能,LMK是被动杀死进程的,Android应用通过AMS,利用proc文件系统更新进程信息。

1.2 LMK流程

我们都知道,AMS负责了系统中四大组件的启动、切换、调度以及应用进程管理和调度工作。在APP使用过程中,AMS会根据四大组件的生命周期在mLruProcess中实时设置对应进程的adj值(更新进程优先级),在内存低于LMK杀死进程的阈值时,LMK会选择adj值最大(如果adj值相等,选择adj中内存占用最大)的进程杀掉,释放内存。

LMK的流程图如下所示3
【性能】OOM原理解析:LowMemoryKiller原理
如图所示,当应用A退回后台,应用B被唤起时,AMS会通过proc更新内核进程列表里应用的adj值,当内存监测器监测到内存达到adj阈值时,就会让LMK选择adj值最大的杀死,释放内存。

2 Android应用进程优先级

Android会尽可能长时间地保持应用存活,但当应用启动过多,内存不足时,系统会选择杀死旧的进程来释放内存。在选择要杀死进程的时候,系统会根据进程运行状态做出评估,选择比较“不重要”的进程杀死。在本地的Android文档中(file:///盘符:/androidsdk目录/docs/guide/components/processes-and-threads.html),应用进程被划分为5个等级,分别是:

  1. 前台进程
  2. 可见进程
  3. 服务进程
  4. 后台进程
  5. 空进程
  • 前台进程
    用户当前正在操作的进程,如果一个进程满足以下任意一个条件,则视为前台进程
  1. 正在交互的Activity(onResume()方法已被调用)
  2. 绑定到正在交互的ActivityService
  3. 正在“前台”运行的Service(服务已调用startForeground)
  4. 正执行一个生命周期回调的Service(onCreate()、onStart()或onDestroy)
  5. 一个正执行其onReceive()方法的BroadcastReceiver

通常,在任意给定时间前台进程都为数不多。只有在内存不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。

  • 可见进程
    没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:
  1. 包含不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一Activity,则有可能会发生这种情况。
  2. 包含绑定到可见(或前台)Activity 的 Service。

可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

  • 服务进程
    使用 startService() 方法启动的且正在运行的服务,并且不属于上述两个更高级别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统会让服务进程保持运行状态。

  • 后台进程
    包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity会恢复其所有可见状态。

  • 空进程
    不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间,这就是所谓热启动 。为了使系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

在谷歌开发者官网中(Processes and Application Lifecycle),进程被分为四类4,分别是:

  • 前台进程
  • 可见进程
  • 服务进程
  • 缓存进程

这里是将空进程后台进程合并成了缓存进程

3 adj的概念与adj级别控制

3.1 adj概念

在Android的LMK机制中,会对所有进程进行分类,对于每一类别的进程会有其oom_adj值的取值范围,oom_adj值越高,则代表进程越不重要。在系统内存较低,执行低杀操作时,会从oom_adj值最高的开始杀。系统LMK机制对于进程的级别以变量形式定义在
framework/base/core/java/com/android/server/am/ProcessList.java类中。

3.2 adj优先级

ADJ优先级 优先级 对应场景
UNKNOWN_ADJ 16 一般指将要会缓存进程,无法获取确定值
CACHED_APP_MAX_ADJ 15 不可见进程的adj最大值(不可见进程可能在任何时候被杀死)
CACHED_APP_MIN_ADJ 9 不可见进程的adj最小值(不可见进程可能在任何时候被杀死)
SERVICE_B_AD 8 B List中的Service(较老的、使用可能性更小)
PREVIOUS_APP_ADJ 7 上一个App的进程(比如APP_A跳转APP_B,APP_A不可见的时候,A就是属于PREVIOUS_APP_ADJ)
HOME_APP_ADJ 6 Home进程
SERVICE_ADJ 5 服务进程(Service process)
HEAVY_WEIGHT_APP_ADJ 4 后台的重量级进程,system/rootdir/init.rc文件中设置
BACKUP_APP_ADJ 3 备份进程(这个不太了解)
PERCEPTIBLE_APP_ADJ 2 可感知进程,比如后台音乐播放
VISIBLE_APP_ADJ 1 可见进程(可见,但是没能获取焦点,比如新进程仅有一个悬浮Activity,Visible process)
FOREGROUND_APP_ADJ 0 前台进程(正在展示是APP,存在交互界面,Foreground process)
PERSISTENT_SERVICE_ADJ -11 关联着系统或persistent进程
PERSISTENT_PROC_ADJ -12 系统persistent进程,比如telephony
SYSTEM_ADJ -16 系统进程
NATIVE_ADJ -17 native进程(不被系统管理)

未完待续

后续:
Android应用的优先级是如何更新的
Android5.0之后框架层的实现:LMKD服务
LomemoryKiller内核部分:如何杀死

参考文章


  1. Android LowMemoryKiller原理分析 ↩︎

  2. Android后台杀死系列之三:LowMemoryKiller原理(4.3-6.0) ↩︎

  3. lowmemorykiller总结 ↩︎

  4. Processes and Application Lifecycle ↩︎