Android Memory Overview [Part1] - Introduction to Low Memory Killer

Introduction

Inspite of the world seeing a flood of devices with ever increasing Memory hardware support [Even the low end Android Devices come equipped with ~2GB
+ of RAM], Android low memory conditions can happen because of various reasons.  

Under these conditions the device may become unusable if certain operations to revive the low/no memory condition are not performed.
We will in this series try and understand the LowMemoryKiller implemented by Android to overcome the conditions of Low/No Memory.

Why the OOM-killer?

Major distribution kernels set the default value of /proc/sys/vm/overcommit_memory to zero, which means that processes can request more memory than is currently free in the system. This is done based on the heuristics that allocated memory is not used immediately, and that processes, over their lifetime, also do not use all of the memory they allocate. Without overcommit, a system will not fully utilize its memory, thus wasting some of it. Overcommiting memory allows the system to use the memory in a more efficient way, but at the risk of OOM situations.

Memory-hogging programs can deplete the system's memory, bringing the whole system to a grinding halt. This can lead to a situation, when memory is so low, that even a single page cannot be allocated to a user process, to allow the administrator to kill an appropriate task, or to the kernel to carry out important operations such as freeing memory. In such a situation, the OOM-killer kicks in and identifies the process to be the sacrificial lamb for the benefit of the rest of the system.

Users and system administrators have often asked for ways to control the behavior of the OOM killer. To facilitate control, the /proc/<pid>/oom_adj knob was introduced to save important processes in the system from being killed, and define an order of processes to be killed. The possible values of oom_adj range from -17 to +15. The higher the score, more likely the associated process is to be killed by OOM-killer. If oom_adj is set to -17, the process is not considered for OOM-killing.

Why the OOM-killer is not present in Android Based system even though it uses the Linux Kernel?

Well, It turns out that the memory expectations from the different Android components are very diversified [viz: Applications, Background service, System & Core Services, HAL’s, App-Processes].

Android did try to exploit [Or should I say use] the OOM Killer of Linux kernel by adding LowMemoryKiller driver in the kernel/staging/android/, However this has not been mainlined yet.

This problem has been solved by Android using lmkd [Low Memory Killer Daemon] in the userspace which uses cgroups and memory pressure monitoring for killing processes or Applications.
In this tutorial, We will understand the philosophy and implementation of the lmkd and Android’s memory management overview.

Android Low Memory Killer Introduction.

LowMemoryKiller (low memory killer) is a multi-level oomKiller extended by Andorid based on the oomKiller principle.
It is also a mechanism of memory recycling which gets triggered according to the memory threshold level.

When the available memory of the system is low, lmk [We will use lmkd & lmk interchangeably] will selectively kill the process. It is more flexible than OOMKiller.
Before we begin our deepdive in understanding lmkd, Let us answer ourselves if we had to design such a component for effectively managing memory how would we do it? , What functionality or sub components it would need?.

Answers: The following should have been the definitive principles or components which would be required for lmkd design and implementation.
·        Definition of Priority [Who should be killed first]
·        The above priority assignment should be dynamic in Nature and should change as per the increase or decrease in pressure
·        Should treat different kind of processes differently eg: Apps, Services, User Processes
·        Must be capable of assigning scores in different situations Eg: The priority score of a Foreground process switching to the background must be reduced.
·        The ability to decide on how many number of processes must/should be killed at any given instance. Eg: One or All.

The above criteria or requirements and much more have been addressed by LowMemoryKiller in Android.

Android uses cgroups underneath to determine this memory crunch situations.

Also remember that when a userspace App process gets killed, The ActivityManager doesn’t come to know of it till the App is woken up by ActivityManagerService. We will understand the Java->Native Killing in details later.

Android application Process Priority and oomAdj

Android keeps applications alive for as long as possible, but in order to create or run more important processes, It may need to remove the old process/es to reclaim memory.
Before Android selects the processes to kill, the system will evaluate based on the running state of the process.
If it needs to reduce memory, the system will first eliminate the least important process, then the less important process, and so on, to recover system resources.
In Android, the application process is divided into 5 levels.
·        Foreground process
·        Visible process
·        Service process
·        Background process
·        Empty process

Each one of the above has a fixed oom_score which keeps getting reassessed with every iteration.

Let us understand the above Process states in detail before we deep dive in the lmkd implementation.

Foreground process

The process necessary for the user's current operation. If a process meets any of the following conditions, it is considered a foreground process:

·        Contains the interacting Activity (resumed
·        Service that contains the activity bound to it
·        Contains the Service that is running in the "foreground" (the service has called startForeground ())
·        Contains the Service that is executing a lifecycle callback (onCreate (), onStart (), or onDestroy ())
·        Contains a BroadcastReceiver that is executing its onReceive () method

Generally, there are only a few foreground processes at any given time. The system will only terminate them if the memory is not enough to support them continuing to run.
At the point of killing the foreground processes, the device has often reached the memory paging state, so some foreground processes need to be terminated to ensure
the user interface is responsive.


Visible process

Even when not in foreground a process can still affect what the user sees on the screen. If a process meets any of the following conditions, it is considered a visible process:

·        Contains activities that are not in the foreground but are still visible to the user (its onPause () method has been called). For example, if the foreground activity starts a dialog box that allows the previous activity to be displayed underneath or afterwards.
·        Contains the Service bound to the visible (or foreground) Activity.
Visible processes are regarded as extremely important processes, unless the system must be terminated in order to maintain all foreground processes running at the same time, otherwise the system will not terminate these processes.

Service process

These are the Processes that are running services that have been started using the startService () method and are not part of the two higher-level processes above. Although the service process is not directly related to what the user sees, they usually perform some operations that the user cares about (for example, playing music in the background or downloading data from the network). Therefore, unless the memory is insufficient to keep all foreground processes and visible processes running at the same time, the system will keep the service process running.

Background process

The process belonging to the Activity that is currently invisible to the user (the Activity's onStop () method has been called). These processes have no direct impact on the user experience, and the system may terminate them at any time to reclaim memory for use by foreground processes, visible processes, or service processes. There are usually many background processes running, so they are saved in the LRU (Least Recently Used) list to ensure that the last process that contains the Activity that the user recently viewed was terminated. If an Activity correctly implements the lifecycle method and saves its current state, terminating its process will not have a significant impact on the user experience, because when the user navigates back to the Activity, the Activity will restore all its visible state.

Empty process

Processes without any active application components. The sole purpose of keeping such a process is to use it as a cache to reduce the startup time required to run the components in it next time. This is also called as warm start . In order to balance system resources between the process cache and the underlying kernel cache, the system often terminates these processes.

Dynamic Re-Assessment and Ranking

Based on the importance of the currently active components in the process, Android rates the process as the highest level it may reach. For example, if a process hosts a service and a visible activity, the process will be rated as a visible process, not a service process.
In addition, the level of a process may be increased due to the dependence of other processes on it, that is, the level of a process serving another process will never be lower than the process it serves. For example, if the content provider in process A serves the client in process B, or if the service in process A is bound to a component in process B, then process A is always considered at least as important as process B.

OOM_ADJ  for Processes in Various states

Shamelessly copied from

// OOM adjustments for processes in various states:
// This is a process only hosting activities that are not visible,
// so it can be killed without any disruption.
static final int CACHED_APP_MAX_ADJ = 15;
static int CACHED_APP_MIN_ADJ = 9;
// The B list of SERVICE_ADJ -- these are the old and decrepit
// services that aren't as shiny and interesting as the ones in the A list.
static final int SERVICE_B_ADJ = 8;
// This is the process of the previous application that the user was in.
// This process is kept above other things, because it is very common to
// switch back to the previous app.  This is important both for recent
// task switch (toggling between the two top recent apps) as well as normal
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
static final int PREVIOUS_APP_ADJ = 7;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
// because the user interacts with it so much.
static final int HOME_APP_ADJ = 6;
// This is a process holding an application service -- killing it will not
// have much of an impact as far as the user is concerned.
static final int SERVICE_ADJ = 5;
// This is a process currently hosting a backup operation.  Killing it
// is not entirely fatal but is generally a bad idea.
static final int BACKUP_APP_ADJ = 4;
// This is a process with a heavy-weight application.  It is in the
// background, but we want to try to avoid killing it.  Value set in
// system/rootdir/init.rc on startup.
static final int HEAVY_WEIGHT_APP_ADJ = 3;
// This is a process only hosting components that are perceptible to the
// user, and we really want to avoid killing them, but they are not
// immediately visible. An example is background music playback.
static final int PERCEPTIBLE_APP_ADJ = 2;
// This is a process only hosting activities that are visible to the
// user, so we'd prefer they don't disappear.
static final int VISIBLE_APP_ADJ = 1;
// This is the process running the current foreground app.  We'd really
// rather not kill it!
static final int FOREGROUND_APP_ADJ = 0;
// This is a system persistent process, such as telephony.  Definitely
// don't want to kill it, but doing so is not completely fatal.
static final int PERSISTENT_PROC_ADJ = -12;
// The system process runs at the default adjustment.
static final int SYSTEM_ADJ = -16;

In the next tutorial we will see the detailed implementation of the lmkd in Android.

Comments

Popular posts from this blog

Android Audio Tutorial [Part Three] : AudioFlinger Introduction and Initialization

Android External Storage Support: Volume Daemon (vold) Architecture

Android Audio Tutorial [Part One] : Introduction