October 18, 2021

How to diagnose and fix errors caused by high memory pressure situations in Android apps

Memory is one of the most important assets your Android application has. The Android platform tries to keep as many applications in memory as possible, even when they’re not in use. This isn’t simply a cache of the files that make up your application, but the running application process and its threads. This behaviour makes switching between applications and tasks quick and keeps a device feeling “snappy” to the end users. On lower-end devices however things can start to get messy if a running application is taking up more than its fair-share of memory. The result of this situation is an increase in what is called “memory pressure”.

You can think of memory pressure as the primary application, the one the user is interacting with, squeezing the other applications out of memory. This is a process and doesn’t happen all at once, or all of the time. Instead the Android platform (specifically the Low Memory Killer or LMK) will take incremental steps in an attempt to free-up more RAM for a memory-hungry foreground application.

To a degree not seen on other platforms Android is largely composed as an ecosystem of interdependent applications, communicating with each other using shared memory or Inter Process Communication (IPC). Many of the service classes you retrieve with Context.getSystemService are in fact lightweight facades that communicate with the system service processes using IPC. This makes for an amazingly interconnected system, where apps can safely share data and resources smoothly and where the whole experience becomes “more than the sum of its parts” for the end-user. It can also lead to unforeseen, and difficult to debug stability issues.

How memory pressure affects app stability

So how does memory pressure affect your application stability? The most common and obvious answer is Out Of Memory errors, but on Android there are several other possible cases. If your application is in the foreground then it is other applications that are being terminated, and if your application is in the background the user won’t notice if it’s killed, right? Wrong.

Your application can be terminated because other applications are consuming too much memory, and if your application is doing any work at the time (backup, server-sync, cache pruning, etc.) you may end up with a crash report that doesn’t appear to be memory related.

Another memory situation that can cause problems is when your application is killed as a user navigates from one application to another. For example when sharing a photo with another application, or navigating to a help page hosted in Chrome. If there isn’t enough memory to keep both applications in memory at the same time, Android will terminate the application in the background and restore it when the user navigates back. This doesn’t seem like a problem at first, but your application may have to cold-start leading to significant delays and potentially Application Not Responding (ANR) reports, as the system thrashes to both restart your application and unload the previous one. This can be made even worse in use cases such as content sharing. For example sending a photo from your amazing photo editing app to a friend over a messaging app.

End-to-end memory reporting in Bugsnag

Bugsnag reports on Android recently received a refresh. As of version 5.12.0 we’ve added some new parameters that we automatically report to your dashboard.

Bugsnag divides memory reporting into two existing metadata categories: App, and Device. The App memory properties are specific to your application, while the Device properties are there to give you an idea what else is happening on the device at the same point in time. We divide these fields into two main categories (as with many other fields we capture): App (specific to your app), and Device (specific to the device your app was running on when the report was generated).

Under the App tab on the Bugsnag Dashboard we report:

  • totalMemory
    The total amount of memory currently available to your app, reported directly from Runtime.totalMemory. The exact definition of this value varies from one device to another, but it’s significance stays the same: this is how much space your Java / Kotlin objects take up according to the platform. This typically includes some unused (free) memory that can be used for allocating new objects without enlarging the heap.
  • freeMemory
    The amount of available memory not currently consumed by Java / Kotlin objects, reported directly from Runtime.freeMemory. This is how much space can be consumed by new objects before the Garbage Collector needs to be run, or more heap space will be allocated.
  • memoryUsage
    A convenience value reported as totalMemory - freeMemory. This is an estimate of how much memory your app was using when the report was generated.
  • memoryLimit
    The maximum amount of memory that Android will allow your app to use for Java / Kotlin objects, reported directly from Runtime.maxMemory. The value reported here will vary widely depending on the device configuration and exact Android version. Some devices will report a value close to the amount of installed RAM, while others will report values as small as 8mb.
  • lowMemory
    Whether the platform reported a low-memory situation to your app before the report was generated. This value is derived from the more detailed memoryTrimLevel, and is provided mostly for backwards compatibility.
  • memoryTrimLevel
    The most recent trim level reported to your app. Discussed in more detail below, this field gives you an indication of the system memory pressure, and the priority of your app when the report was generated.

Under the Device tab we report:

  • totalMemory
    As it says on the box: the total amount of memory available to the device. This varies a little based on device configuration and Android version, but is most typically the amount of physically installed RAM.
  • freeMemory
    The amount of memory that Android considered to be “free” when the report was generated. This is often quite low as Android will maintain large amounts of cached data in memory, which doesn’t count as “free”.

Memory Trim Level

One significant change is that we now track the Memory Trim Level in the App tab for both JVM and NDK reports. This allows you to see what sort of memory pressure was last reported to your app:

This attribute is the most recent value reported to your application using onTrimMemory, providing valuable insight into why that Bitmap allocation failed, or why your application was terminated. We’ve also started automatically collecting breadcrumbs when these events are delivered, so you’ll have a view of the memory pressure leading up to a crash report.

The Device tab also includes a clear idea of the memory situation for the physical device, including the amount of installed RAM (totalMemory) and how much of that memory is considered to be free and available (freeMemory). Knowing how much RAM is available on a device can lead to valuable insights when a user has managed to crash a memory-heavy feature in your application.

Not all bugs are worth fixing. Only fix the ones that matter.

While memory is typically not a problem on flagship and mid-tier devices, many commodity devices in the Android ecosystem can cause strange and unexpected stability issues under high memory pressure situations. Knowing what features of your app were being accessed; the memory pressure leading up to a crash; and the overall memory health of a device can help you find the real source of these errors. Knowing when an error is caused by memory pressure from your own app — or another app —  makes it easier to decide which are worth fixing, and which can be safely left alone.

If you’re currently using Bugsnag to manage the stability of your Android application, upgrade to our latest notifier version to start capturing end-to-end memory diagnostics. Please don’t hesitate to contact Support if you have any questions or feedback.

If you’re new to Bugsnag, start a 14 day free trial or request a demo to see this and other capabilities of our stability management platform in action.

BugSnag helps you prioritize and fix software bugs while improving your application stability
Request a demo