Android has a unique memory management model. Below, are the notes taken while we try to understand it thoroughly.
The ActivityManager, among other things, is responsible for making sure that apps that are most important to the user and/or essential remain active. It does this by dynamically assigning values that roughly indicate the app's importance. When memory gets low these values are used to find the apps that need to be "trimmed." The memory from these "trimmed" apps is freed, making more available for the more important apps.
As mentioned above, apps are dynamically ranked into importance categories, called adjustments, by the ActivityManager. The adjustments are defined in the ProcessList.java file and include the following:
Note: A lower value has higher priority. This list is ordered from lowest to highest priority. Descriptions are from the inline code comments.
HIDDEN_APP_MAX_ADJ (15)::
This is a process only hosting activities that are not visible, so it can be killed without any disruption.
HIDDEN_APP_MIN_ADJ (9)::
The bottom range of values fitting the above description.
SERVICE_B_ADJ (8)::
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.
PREVIOUS_APP_ADJ (7)::
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 and then pressing back to return to e-mail.
HOME_APP_ADJ (6)::
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.
SERVICE_ADJ (5)::
This is a process holding an application service -- killing it will not have much of an impact as far as the user is concerned.
BACKUP_APP_ADJ (4)::
This is a process currently hosting a backup operation. Killing it is not entirely fatal but is generally a bad idea.
HEAVY_WEIGHT_APP_ADJ (3)::
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.
PERCEPTIBLE_APP_ADJ (2)::
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.
VISIBLE_APP_ADJ (1)::
This is a process only hosting activities that are visible to the user, so we'd prefer they don't disappear.
FOREGROUND_APP_ADJ (0)::
This is the process running the current foreground app. We'd really rather not kill it!
PERSISTENT_PROC_ADJ (-12)::
This is a system persistent process, such as telephony. Definitely don't want to kill it, but doing so is not completely fatal.
SYSTEM_ADJ (-16)::
The system process runs at the default adjustment.
Notice that the lowest two adjustments have a MAX and MIN values. This is because there are is also a more fine-grained ranking system that is internal to ActivityManagerService. For example, empty apps are those apps that are hidden but no longer have an activity associated with it.
The adjustments are used by the Linux kernel to determine which process should be terminated. The kernel knows about these adjustments via the sysfs interface to lowmemorykiller. The lowmemorykiller is a kernel module that moves the roll of managing out of memory (OOM) issues to the user space. It does this by allowing the user-space to maintain a list of adjustments and a corresponding list of thresholds (units are in memory pages - usually 4K) at which point processes with these adjustments should be killed.
While the management is moved to user-space, the actually killing of process is done by the lowmemorykiller. The lowmemorykiller's schrinker is registered and with the kernel run by the kwapd kernel thread.
The interfaces for lowmemorykiller can be found in the /sys/module/lowmemorykiller/parameters/ directory.
adj::
Comma separated list of adjustments. Example: ```0,1,2,4,10,15```
cost::
This used to set the value of lowmem_shrinker.seeks. This value determines how many slab objects shrinker() should scan and try to reclaim. Default ```32```
debug_level::
Debug level. Can be set to a range of 0-5, with 5 being most verbose.
minfree::
Comma separated list of page table thresholds. Example: ```1024,2304,4096,9216,12544,16384```
Valid values for the adjustments are defined in oom.h
/*
* /proc/<pid>/oom_adj is deprecated, see
* Documentation/feature-removal-schedule.txt.
*
* /proc/<pid>/oom_adj set to -17 protects from the oom-killer
*/
#define OOM_DISABLE (-17)
/* inclusive */
#define OOM_ADJUST_MIN (-16)
#define OOM_ADJUST_MAX 15
/*
* /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
Note: Although oom_adj is deprecated in upstream kernels, it's still supported and it's what Android uses.
The challenge is to find a set of values for the lowmemorykiller that all the system to kill off process when memory gets tight in a way that the user won't notice or at the least not be bothered by.
The oom_adj value is continuously updated by !ActivityManagerService. The values are finally written to /proc/{pid}/oom_adj in the android_os_Process_setOomAdj
method of android_util_Process.cpp
which is invoked by !ActivityManagerService's updateOomAdjLocked
method.
Applications have a couple methods they can use to proactively help when memory pressure is increasing. The first line of defense is to override and handle [http://developer.android.com/reference/android/content/ComponentCallbacks2.html#onTrimMemory(int) onTrimMemory]. onTrimMemory takes a parameter which indicates the current memory pressure. It's the most granular way to manage memory. For a description of the see the [http://developer.android.com/reference/android/content/ComponentCallbacks2.htm ComponentCallbacks2 class overview]. The last line of defense is [http://developer.android.com/reference/android/app/Activity.html#onLowMemory() onLowMemory]. This method takes no arguments and is called only when the background LRU list is empty, meaning all trimming efforts have been exhausted.
In order for these to be called !ActivityManager keeps track of processes via instances of the !ProcessRecord class. A !ProcessRecord object keeps track of all aspects of a running process. The actual process is accessible via the !ProcessRecord's thread member. The thread implements !IApplicationThread whose contract includes methods for scheduling onLowMemory (scheduleLowMemory) and onTrimMemory (scheduleTrimMemory).
The following properties can control how Dalvik manages its heap.
Property | Default | Variable in code | Notes |
---|---|---|---|
dalvik.vm.heapsize | 16m | !DvmGlobals.heapMaximumSize | This is the max heap size when an app sets the largeHeap property to true in the AndroidManifest.xml. |
dalvik.vm.heapgrowthlimit | dalvik.vm.heapsize | !DvmGlobals.heapgGrowthLimit | This the typical max heap size, meaning when the largeHeap property is not true. This should typically be no more that half the heapsize value. |
dalvik.vm.heaptargetutilization | 0.50 | !DvmGlobals.heaptargetUtilization | The ideal ratio of live to free memory. Is clamped to have a value between 0.2 and 0.8. |
dalvik.vm.heapmaxfree | 2m | !DvmGlobals.heapMaxFree | Forces the free memory to never be larger than the given value. |
dalvik.vm.heapminfree | dalvik.vm.heapmaxfree / 4 | !DvmGlobals.heapMinFree | Forces the free memory to never be smaller than the given value. |
dalvik.vm.heapstartingsize | 2m | !DvmGlobals.heapStartingSize | The initial starting size of the heap from which it will grow to growthLimit. |
Note: Values are in bytes, can be given single-letter multipliers (k, m, g), and must be divisible by 1024.
You'll often see Dalvik output of the following form.
D/dalvikvm( 193): GC_EXPLICIT freed 39K, 66% free 2349K/6788K, paused 24ms+8ms, total 129ms
For the most part this is self-explanatory. However, the GC_EXPLICIT, in the instance is telling us what type of GC is being done. There are 4 types of GC defined in Heap.cpp.
- GC_FOR_ALLOC Not enough space for an "ordinary" Object to be allocated.
- GC_CONCURRENT Automatic GC triggered by exceeding a heap occupancy threshold.
- GC_EXPLICIT Explicit GC via Runtime.gc(), VMRuntime.gc(), or SIGUSR1.
- GC_BEFORE_OOM Final attempt to reclaim memory before throwing an OOM.
Save the last type, all of these should be expected to happen during normal operation.
Android and Linux provide us several ways to monitor the state of process and memory management on a running system.
The procfs directory /proc/[pid]/ has a number of pseudo files that can be used for viewing process state. See [http://man7.org/linux/man-pages/man5/proc.5.html the procfs man page] for a detailed list. Of most interest to us is /proc/[pid]/oom_adj. This file is where we can see which adjustment level the !ActivityManagerService has assigned to a process. The values correspond with the levels shown above and set in !ProcessList.
The dumpsys command can tell us almost anything we want to know about the system and a particular time. The most interesting for us are the dumpsys meminfo
& dumpsys activity oom
. These commands tell us and applications adjustment state, current memory usage, it's garbage collecting state and much more.
An example of the output from dumpsys meminfo
follows.
18642 kB: system (pid 451)
18349 kB: com.android.launcher (pid 3106)
14552 kB: android.process.acore (pid 3133)
9884 kB: com.android.systemui (pid 1791)
9498 kB: com.google.process.gapps (pid 735)
7078 kB: com.google.process.location (pid 667)
5623 kB: com.android.inputmethod.latin (pid 646)
5566 kB: com.google.android.syncadapters.calendar (pid 3270)
5475 kB: com.google.android.apps.genie.geniewidget (pid 3185)
4940 kB: com.google.android.gsf.login (pid 3254)
4362 kB: com.android.browser (pid 3359)
4095 kB: com.android.phone (pid 689)
3898 kB: com.android.calendar (pid 3305)
3691 kB: com.android.providers.calendar (pid 3286)
3360 kB: com.cyanogenmod.lockclock (pid 3327)
2810 kB: redacted (pid 3117)
2599 kB: com.android.location.fused (pid 676)
2319 kB: com.android.smspush (pid 795)
Total PSS by OOM adjustment:
18642 kB: System
18642 kB: system (pid 451)
13979 kB: Persistent
9884 kB: com.android.systemui (pid 1791)
4095 kB: com.android.phone (pid 689)
18349 kB: Foreground
18349 kB: com.android.launcher (pid 3106)
18895 kB: Visible
9498 kB: com.google.process.gapps (pid 735)
7078 kB: com.google.process.location (pid 667)
2319 kB: com.android.smspush (pid 795)
8222 kB: Perceptible
5623 kB: com.android.inputmethod.latin (pid 646)
2599 kB: com.android.location.fused (pid 676)
2810 kB: A Services
2810 kB: redacted (pid 3117)
45844 kB: Background
14552 kB: android.process.acore (pid 3133)
5566 kB: com.google.android.syncadapters.calendar (pid 3270)
5475 kB: com.google.android.apps.genie.geniewidget (pid 3185)
4940 kB: com.google.android.gsf.login (pid 3254)
4362 kB: com.android.browser (pid 3359)
3898 kB: com.android.calendar (pid 3305)
3691 kB: com.android.providers.calendar (pid 3286)
3360 kB: com.cyanogenmod.lockclock (pid 3327)
Total PSS by category:
60792 kB: Dalvik
32872 kB: Unknown
14517 kB: .dex mmap
12536 kB: .so mmap
4856 kB: .apk mmap
700 kB: Other mmap
256 kB: Native
76 kB: Ashmem
72 kB: Other dev
44 kB: .ttf mmap
16 kB: Cursor
4 kB: .jar mmap
Total PSS: 126741 kB
KSM: 6672 kB saved from shared 1128 kB
24552 kB unshared; 102936 kB volatile
The output from this command is pretty straight forward so I won't describe it detail.
If you provide dumpsys meminfo
with a package name, such as org.mozilla.firefox, you get more detailed output about that process.
root@oc:/ # dumpsys meminfo org.mozilla.firefox
Applications Memory Usage (kB):
Uptime: 4117434 Realtime: 8929976
** MEMINFO in pid 3400 [org.mozilla.firefox] **
Shared Private Heap Heap Heap
Pss Dirty Dirty Size Alloc Free
------ ------ ------ ------ ------ ------
Native 16 8 16 4492 4052 439
Dalvik 12056 4764 11624 10912 6252 4660
Cursor 0 0 0
Ashmem 14620 24 14608
Other dev 4 20 0
.so mmap 3151 1876 460
.jar mmap 0 0 0
.apk mmap 766 0 0
.ttf mmap 32 0 0
.dex mmap 5641 0 0
Other mmap 387 16 84
Unknown 74698 428 74672
TOTAL 111371 7136 101464 15404 10304 5099
Objects
Views: 107 ViewRootImpl: 1
AppContexts: 3 Activities: 1
Assets: 2 AssetManagers: 2
Local Binders: 23 Proxy Binders: 19
Death Recipients: 0
OpenSSL Sockets: 0
SQL
MEMORY_USED: 540
PAGECACHE_OVERFLOW: 214 MALLOC_SIZE: 154
DATABASES
pgsz dbsz Lookaside(b) cache Dbname
4 72 48 10/26/6 /data/data/org.mozilla.firefox/files/mozilla/7bx4gz6c.default/health.db
4 880 172 35/51/14 /data/data/org.mozilla.firefox/files/mozilla/7bx4gz6c.default/browser.db
4 880 108 32/23/9 /data/data/org.mozilla.firefox/files/mozilla/7bx4gz6c.default/browser.db (2)
Asset Allocations
zip:/data/app/org.mozilla.firefox-1.apk:/resources.arsc: 531K
For an explanation of this see Dianne Hackborn's StackOverflow description. Dianne is a core Android engineer, responsible for important stuff like Binder.
An example of the output from dumpsys activity oom
follows.
OOM levels:
SYSTEM_ADJ: -16
PERSISTENT_PROC_ADJ: -12
FOREGROUND_APP_ADJ: 0
VISIBLE_APP_ADJ: 1
PERCEPTIBLE_APP_ADJ: 2
HEAVY_WEIGHT_APP_ADJ: 3
BACKUP_APP_ADJ: 4
SERVICE_ADJ: 5
HOME_APP_ADJ: 6
PREVIOUS_APP_ADJ: 7
SERVICE_B_ADJ: 8
HIDDEN_APP_MIN_ADJ: 10
HIDDEN_APP_MAX_ADJ: 15
Process OOM control:
PERS # 7: adj=sys /F trm=15 451:system/1000 (fixed)
oom: max=-16 hidden=10 client=10 empty=10 curRaw=-16 setRaw=-16 cur=-16 set=-16
keeping=true hidden=false empty=false hasAboveClient=false
PERS #10: adj=pers /F trm=15 689:com.android.phone/1001 (fixed)
oom: max=-12 hidden=10 client=10 empty=10 curRaw=-12 setRaw=-12 cur=-12 set=-12
keeping=true hidden=false empty=false hasAboveClient=false
PERS # 3: adj=pers /F trm=15 1791:com.android.systemui/u0a10030 (fixed)
oom: max=-12 hidden=10 client=10 empty=10 curRaw=-12 setRaw=-12 cur=-12 set=-12
keeping=true hidden=false empty=false hasAboveClient=false
Proc # 0: adj=fore /FA trm=15 3106:com.android.launcher/u0a10022 (top-activity)
oom: max=15 hidden=10 client=10 empty=10 curRaw=0 setRaw=0 cur=0 set=0
keeping=true hidden=false empty=false hasAboveClient=false
Proc # 9: adj=vis /F trm=15 795:com.android.smspush/u0a10032 (service)
com.android.smspush/.WapPushManager<=Proc{689:com.android.phone/1001}
oom: max=15 hidden=10 client=10 empty=10 curRaw=1 setRaw=1 cur=1 set=1
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 8: adj=vis /B trm=15 735:com.google.process.gapps/u0a10033 (provider)
com.google.android.gsf/.settings.GoogleSettingsProvider<=Proc{667:com.google.process.location/u0a10033}
oom: max=15 hidden=10 client=10 empty=10 curRaw=1 setRaw=1 cur=1 set=1
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 6: adj=vis /B trm=15 667:com.google.process.location/u0a10033 (service)
com.google.android.location/.NetworkLocationService<=Proc{451:system/1000}
oom: max=15 hidden=10 client=10 empty=10 curRaw=1 setRaw=1 cur=1 set=1
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 1: adj=vis /B trm=15 3133:android.process.acore/u0a10001 (provider)
com.android.providers.contacts/.ContactsProvider2<=Proc{735:com.google.process.gapps/u0a10033}
oom: max=15 hidden=10 client=10 empty=10 curRaw=1 setRaw=1 cur=1 set=1
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 5: adj=prcp /F trm=15 646:com.android.inputmethod.latin/u0a10021 (service)
com.android.inputmethod.latin/.LatinIME<=Proc{451:system/1000}
oom: max=15 hidden=10 client=10 empty=10 curRaw=2 setRaw=2 cur=2 set=2
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 4: adj=prcp /B trm=15 676:com.android.location.fused/u0a10018 (service)
com.android.location.fused/.FusedLocationService<=Proc{451:system/1000}
oom: max=15 hidden=10 client=10 empty=10 curRaw=2 setRaw=2 cur=2 set=2
keeping=true hidden=false empty=true hasAboveClient=false
Proc # 2: adj=svc /B trm=15 3117:redacted/u0a10014 (started-services)
oom: max=15 hidden=10 client=10 empty=10 curRaw=5 setRaw=5 cur=5 set=5
keeping=true hidden=false empty=true hasAboveClient=false
Processes that are waiting to GC:
Process ProcessRecord{422a4260 735:com.google.process.gapps/u0a10033}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
Process ProcessRecord{422f13b0 451:system/1000}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
Process ProcessRecord{4247c388 667:com.google.process.location/u0a10033}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
Process ProcessRecord{4231a630 646:com.android.inputmethod.latin/u0a10021}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
Process ProcessRecord{4250cfe0 676:com.android.location.fused/u0a10018}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
Process ProcessRecord{422f2dd8 1791:com.android.systemui/u0a10030}
lowMem=true, last gced=3110616 ms ago, last lowMem=18198 ms ago
mHomeProcess: ProcessRecord{422e4ce8 3106:com.android.launcher/u0a10022}
mPreviousProcess: null
The "Process OOM control" section of this output is a list of process sorted by their current adjustment values with a lot of other useful information, some of which needs further explanation.
Let's take a look at the following excerpt.
Proc # 8: adj=vis /B trm=15 735:com.google.process.gapps/u0a10033 (provider)
com.google.android.gsf/.settings.GoogleSettingsProvider<=Proc{667:com.google.process.location/u0a10033}
oom: max=15 hidden=10 client=10 empty=10 curRaw=1 setRaw=1 cur=1 set=1
keeping=true hidden=false empty=true hasAboveClient=false
The table below explains each field, provides a description and notes other possible values or value ranges.
field | type | range/format | Interpretation |
---|---|---|---|
Proc | string | PERS, Proc | Process labeled with "Proc" are normal process that fall under the domain of the lowmemorykiller. Those labeled "PRES" are persistent process and are generally go untouched by the lowmememorykiller. |
# 8 | int | 0 to (!NumberOfProcessRecords - 1) | The position of the process in the Least Recently Used (LRU) process list. |
adj | int | Abbreviation of the adjustments from above. | This is the current adjustment level of the app. |
/B | string | /{B,F}{ ,A,S} | B or F tells us if this is a background or foreground process, respectively. The optional A and S indicate whether this processes is running any foreground activities or services, respectively. |
trm | int | These values corrospond to trim levels defined in [http://developer.android.com/reference/android/content/ComponentCallbacks2.html ComponentCallbacks2] | Last selected memory trimming level. |
735:com.google.process.gapps/u0a10033 | string | pid:process_name:a{userId}u{appId} or pid:process_name:uid | |
(provider) | string | (adjustmentType) | This is the primary factor contributing to calculate the oom_adj value. The logic for determining this value is in !ActivityManagerService's computeOomAdjLocked method. |
This line is printed when there is a either a source or target for the process. The format is source<#target
.
source::
An option dependent object
target::
The target component impacting the OOM adjustment. This impact is mostly determined by the flags past to Context.bindService().
These levels are all adjustment levels and most be in that range (-17 - 15).
field | type | Interpretation |
---|---|---|
max | int | Maximum OOM adjustment for this process. |
hidden | int | If hidden, this is the adjustment to use |
client | int | If empty but hidden client, this is the adjustment to use |
empty | int | If empty, this is the adjustment to use |
curRaw | int | Current OOM unlimited adjustment for this process |
setRaw | int | Last set OOM unlimited adjustment for this process |
cur | int | Current OOM adjustment for this process |
set | int | Last set OOM adjustment for this proces |
field | type | range/format | Interpretation |
---|---|---|---|
keeping | boolean | N/A | Actively running code so it needs to be kept around and not killed. |
hidden | boolean | N/A | Whether this is a hidden process. |
empty | boolean | N/A | Whether this is an empty background process. |
hasAboveClient | boolean | N/A | Whether the process is is bound to a service that it considers more important than itself. The is done by using the BIND_ABOVE_CLIENT flag when calling bindService. This used when one would rather the process die before the service. |
procrank
shows system processes ranked by their memory usage. An example of its output follows.
PID Vss Rss Pss Uss cmdline
451 29508K 29464K 18528K 17384K system_server
3106 83228K 33624K 18489K 16056K com.android.launcher
3133 28804K 28696K 14630K 13032K android.process.acore
1791 33844K 20328K 9935K 8648K com.android.systemui
735 20940K 20864K 9507K 8060K com.google.process.gapps
667 16592K 16508K 7147K 6532K com.google.process.location
646 17136K 14508K 5688K 5068K com.android.inputmethod.latin
3270 19464K 19372K 5653K 3920K com.google.android.syncadapters.calendar
3185 19436K 19340K 5574K 4324K com.google.android.apps.genie.geniewidget
3254 18092K 17980K 5026K 3896K com.google.android.gsf.login
3359 17956K 17860K 4443K 3284K com.android.browser
689 12664K 12568K 4160K 3636K com.android.phone
3305 17360K 17260K 3981K 2864K com.android.calendar
3286 17340K 17240K 3770K 2668K com.android.providers.calendar
218 3992K 3992K 3745K 3656K /system/bin/mediaserver
3327 16304K 16204K 3229K 2144K com.cyanogenmod.lockclock
3117 15684K 15580K 2892K 1928K redacted
676 11996K 11888K 2674K 2024K com.android.location.fused
795 10872K 10764K 2386K 1788K com.android.smspush
216 11392K 11244K 1941K 1280K zygote
214 1964K 1964K 1915K 1908K /system/bin/debuggerd
215 31148K 1772K 1264K 1108K /system/bin/surfaceflinger
3388 1252K 1252K 1075K 1024K procrank
217 700K 700K 673K 672K /system/bin/drmserver
213 820K 820K 573K 548K /system/bin/netd
211 376K 376K 276K 264K /system/bin/vold
220 380K 380K 266K 260K /system/bin/keystore
221 264K 264K 260K 260K /bin/busybox
474 324K 324K 235K 232K /system/bin/sh
222 236K 236K 216K 216K /sbin/adbd
176 240K 240K 195K 180K /sbin/nbd-client
162 240K 240K 195K 180K /sbin/nbd-client
168 240K 240K 195K 180K /sbin/nbd-client
184 240K 240K 195K 180K /sbin/nbd-client
212 328K 328K 192K 180K /system/bin/logcat
1 248K 248K 180K 132K /init
207 204K 204K 140K 136K /system/bin/sdcard
62 172K 172K 120K 72K /sbin/ueventd
219 144K 144K 115K 112K /system/bin/installd
210 132K 132K 101K 100K /system/bin/servicemanager
------ ------ ------
141794K 120136K TOTAL
RAM: 373148K total, 90428K free, 17512K buffers, 59908K cached, 2516K shmem, 12532K slab
Here we see that procrank
gives us more, but uncategorized, information about a processes memory usage. Additionally, the last line gives a system wide view of memory usage.
Since we started this wiki page, Google has added some official documentation regarding memory management.