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.
+ 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
Post a Comment