Android Performance

Overview of Jank and Frame Drops in Android - Low Memory

Word count: 1.8kReading time: 11 min
2019/09/18
loading

In the Overview of Jank and Frame Drops in Android - System Layer article, we touched upon jank cases caused by low system memory. Since low memory has a significant impact on overall device performance, I’m dedicating this article to exploring those effects in depth.

As Android versions evolve and apps become more feature-heavy, their memory requirements increase. However, many devices with 4GB of RAM or less are still in use. These users are prone to low memory situations, especially after major system updates or as they install more apps.

Low memory triggers performance issues categorized by slow responsiveness and jank. Examples include longer app launch times, more frame drops in scrolling, more frequent cold starts as background processes are killed, and increased device temperature. Below, I’ll outline the causes, debugging methods, and potential optimizations for these issues.

  1. Overview of Jank and Frame Drops in Android - Methodology
  2. Overview of Jank and Frame Drops in Android - System Layer
  3. Overview of Jank and Frame Drops in Android - Application Layer
  4. Overview of Jank and Frame Drops in Android - Low Memory

Identifying Low Memory

Meminfo Data

The simplest way is using dumpsys meminfo:

1
2
3
4
5
6
adb shell dumpsys meminfo
...
Total RAM: 7,658,060K (status moderate)
Free RAM: 550,200K ...
Used RAM: 7,718,091K ...
ZRAM: ...

Signs of low memory:

  1. Free RAM is very low, while Used RAM is very high.
  2. ZRAM usage is high (if enabled).

LMK and kswapd Activity

During low memory, the Low Memory Killer Daemon (LMKD) becomes active. You’ll see kill messages in the Kernel Log:

1
2
3
[kswapd0] lowmemorykiller: Killing 'u.mzsyncservice' (15609) (tgid 15609), adj 906,
to free 28864kB on behalf of 'kswapd0' ...
cache 258652kB is below limit 261272kB for oom score 906

This means kswapd0 killed a process because memory fell below the threshold for an OOM score of 900.

/proc/meminfo

This is how the Linux kernel exposes memory stats. When memory is low, MemFree and MemAvailable will be minimal.

System-wide Lag & Slow Response

When the system is in a low memory state, the entire device feels much more sluggish compared to normal memory conditions. Clicking on apps or launching applications will feel unresponsive or slow.

Impact of Low Memory on Performance

Main Thread I/O Latency

Low memory causes significant I/O issues.

  1. In a Systrace, this appears as many yellow/orange thread states: Uninterruptible Sleep | WakeKill - Block I/O.
  2. Block sites usually look like: wait_on_page_bit_killable+0x78/0x88.

The Linux page cache chain sometimes contains pages that aren’t ready (content hasn’t been fully read from disk). If a user accesses such a page, it blocks on wait_on_page_locked_killable. Under heavy I/O, these I/O operations queue up, leading to long block durations.

When I/O is heavy, app operations involving disk access (loading views, reading files, configs, or odex files) trigger Uninterruptible Sleep, significantly slowing down the app.

CPU Contention

Low memory forces the Low Memory Killer to scan and kill processes frequently. kswapd0 (a kernel worker thread) wakes up to perform memory reclamation. If memory sticks near low levels, kswapd0 remains active, consuming CPU cycles and generating heat.

If kswapd0 or HeapTaskDaemon (which handles JVM memory operations) hogs a big core (e.g., CPU 7), the foreground app’s main thread might compete for that core, leading to missed Vsync.

HeapTaskDaemon also typically runs at high frequency during low memory conditions.

, performing memory-related operations.

Process Death-Restart Cycles

As LMK kills “Cached” processes, their parent processes or other apps often immediately restart them, leading to a “death-restart” cycle. This puts immense strain on CPU, Memory, and I/O.

For example, below is the result after a Monkey test, where QQ is frequently killed and restarted in a short period:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
07-23 14:32:16.932  1435  1510 I am_proc_start: [0,30387,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:16.969 1435 3420 I am_proc_bound: [0,30387,com.tencent.mobileqq]
07-23 14:32:16.979 1435 3420 I am_kill : [0,30387,com.tencent.mobileqq,901,empty #3]
07-23 14:32:16.996 1435 3420 I am_proc_died: [0,30387,com.tencent.mobileqq,901,18]
07-23 14:32:17.028 1435 1510 I am_proc_start: [0,30400,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.054 1435 3420 I am_proc_bound: [0,30400,com.tencent.mobileqq]
07-23 14:32:17.064 1435 3420 I am_kill : [0,30400,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.082 1435 3420 I am_proc_died: [0,30400,com.tencent.mobileqq,901,18]
07-23 14:32:17.114 1435 1510 I am_proc_start: [0,30413,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.139 1435 3420 I am_proc_bound: [0,30413,com.tencent.mobileqq]
07-23 14:32:17.149 1435 3420 I am_kill : [0,30413,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.166 1435 3420 I am_proc_died: [0,30413,com.tencent.mobileqq,901,18]
07-23 14:32:17.202 1435 1510 I am_proc_start: [0,30427,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.216 1435 3420 I am_proc_bound: [0,30427,com.tencent.mobileqq]
07-23 14:32:17.226 1435 3420 I am_kill : [0,30427,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.249 1435 3420 I am_proc_died: [0,30427,com.tencent.mobileqq,901,18]
07-23 14:32:17.278 1435 1510 I am_proc_start: [0,30440,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.299 1435 3420 I am_proc_bound: [0,30440,com.tencent.mobileqq]
07-23 14:32:17.309 1435 3420 I am_kill : [0,30440,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.329 1435 2116 I am_proc_died: [0,30440,com.tencent.mobileqq,901,18]
07-23 14:32:17.362 1435 1510 I am_proc_start: [0,30453,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.387 1435 2116 I am_proc_bound: [0,30453,com.tencent.mobileqq]
07-23 14:32:17.398 1435 2116 I am_kill : [0,30453,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.420 1435 2116 I am_proc_died: [0,30453,com.tencent.mobileqq,901,18]
07-23 14:32:17.447 1435 1510 I am_proc_start: [0,30466,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.474 1435 2116 I am_proc_bound: [0,30466,com.tencent.mobileqq]
07-23 14:32:17.484 1435 2116 I am_kill : [0,30466,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.507 1435 2116 I am_proc_died: [0,30466,com.tencent.mobileqq,901,18]
07-23 14:32:17.533 1435 1510 I am_proc_start: [0,30479,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.556 1435 2116 I am_proc_bound: [0,30479,com.tencent.mobileqq]
07-23 14:32:17.566 1435 2116 I am_kill : [0,30479,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.587 1435 2116 I am_proc_died: [0,30479,com.tencent.mobileqq,901,18]
07-23 14:32:17.613 1435 1510 I am_proc_start: [0,30492,10145,com.tencent.mobileqq,restart,com.tencent.mobileqq]
07-23 14:32:17.636 1435 2116 I am_proc_bound: [0,30492,com.tencent.mobileqq]
07-23 14:32:17.646 1435 2116 I am_kill : [0,30492,com.tencent.mobileqq,901,empty #3]
07-23 14:32:17.667 1435 2116 I am_proc_died: [0,30492,com.tencent.mobileqq,901,18]

The corresponding Systrace - SystemServer shows AM (Activity Manager) frequently killing and restarting QQ:

The Kernel section of this trace also shows busy CPU activity:

Memory Reclamation and I/O Triggers

When operations slow down over time, it’s often due to memory reclamation (Fast Path, kswapd, Direct Reclaim, LMK).

  • Reclaiming anonymous pages involves swapping them out.
  • Reclaiming file-backed pages involves writing back and clearing them.
    Both actions, especially those involving files, increase the probability of block I/O.

A common scenario: reopening a recent app feels slow because of a do_page_fault. If the memory was swapped out, it must be swapped in. If it was a regular file, it must be read into the page cache from disk.

do_page_faultlock_page_or_retrywait_on_page_bit_killable checks if the page has the PG_locked flag set. If set, it blocks until PG_locked is cleared. The PG_locked flag is cleared only when writeback starts or I/O read completes. The readahead to pagecache functionality also affects block I/O - if too large, it increases blocking probability.

Case Study: App Startup Under Low Memory

Below is a trace of an app’s cold start under low memory, from bindApplication to the first frame (total 2s).

Low Memory Startup

  • Total time: 2s.
  • Uninterruptible Sleep (I/O block) took ~750ms (vs ~130ms normally).
  • Running time was ~600ms (similar to normal).
  • CPU activity: Many kworker and kswapd0 threads were active alongside the app.

Normal Memory Startup

  • Total time: 1.22s.
  • Uninterruptible Sleep totaled only 130ms.
  • CPU activity: Very few memory/IO-related threads beyond HeapTaskDaemon.

Looking at CPU usage during this period, besides HeapTaskDaemon running more frequently, very few other memory and I/O related processes are active.

Optimization Strategies

Optimizing for low memory requires balancing system control and user experience. Managing third-party apps—many of which use aggressive keep-alive and background services—is crucial.

  1. Tune extra_free_kbytes: Increase the threshold for memory reclamation.
  2. Increase Disk I/O Speeds: Use UFS 3.0 or better storage hardware.
  3. Optimize read_ahead_kb: Avoid excessively high values that increase block probability.
  4. Cgroup blkio: Use cgroups to limit background I/O, prioritizing foreground responsiveness.
  5. Proactive Reclamation: Reclaim memory before the user interacts with the app.
  6. LMK Efficiency: Ensure LMK kills the most effective targets to avoid “death-restart” loops.
  7. Refine swappiness: Balance anonymous page swapping vs. file cache clearing.
  8. OEM Policies: Implement more aggressive process management on low-RAM devices.
  9. User Education: Suggest restarting the phone or clearing background apps when memory is critically low.
  10. Strategy: Alert users (or not alert users) to kill unnecessary background processes when memory is insufficient
  11. Strategy: Prompt users to restart the phone when memory is severely insufficient and cannot recover

References

  1. https://blog.csdn.net/qkhhyga2016/article/details/79540119
  2. https://blog.csdn.net/zsj100213/article/details/82427527

About Me && Blog

Below are personal introductions and related links. Looking forward to exchanging ideas with fellow professionals - when three people walk together, one must be my teacher!

  1. Blogger’s Personal Introduction: Contains personal WeChat and WeChat group links.
  2. Blog Content Navigation: A navigation of personal blog content.
  3. Personally Organized and Collected Excellent Blog Articles - Android Performance Optimization Must-Knows: Welcome self-recommendations and recommendations (private WeChat chat is fine)
  4. Android Performance Optimization Knowledge Planet: Welcome to join, thank you for your support!

One person can walk faster, but a group of people can walk farther

WeChat Scan QR Code

CATALOG
  1. 1. Identifying Low Memory
    1. 1.1. Meminfo Data
    2. 1.2. LMK and kswapd Activity
    3. 1.3. /proc/meminfo
  2. 2. System-wide Lag & Slow Response
  3. 3. Impact of Low Memory on Performance
    1. 3.1. Main Thread I/O Latency
    2. 3.2. CPU Contention
    3. 3.3. Process Death-Restart Cycles
    4. 3.4. Memory Reclamation and I/O Triggers
  4. 4. Case Study: App Startup Under Low Memory
    1. 4.1. Low Memory Startup
    2. 4.2. Normal Memory Startup
  5. 5. Optimization Strategies
  6. 6. References
  7. 7. About Me && Blog