Droids can bleed, too

Federico Ramundo
Wolox
Published in
6 min readDec 1, 2016

--

Is your Android app performing like it should? Does it struggle to run on budget phones?

I hate to break the news, but there is probably a leak in your app.

The garbage collector

Java is a robust language. One of its many features includes the infamous Garbage Collector. Developers tend to rely on this feature blindly, thinking that memory allocation and deallocation is a thing from the past.

However, with great power comes great responsibility: the garbage collector will only release the memory if it’s not used elsewhere.

So, what does this mean? First things first, let’s start by understanding how the Java garbage collector works.

Java applications have a main entry point, where the execution begins and the classes start getting instantiated and methods get called. Think of this point as the “root” of the memory tree. Every variable and field of your code keep a reference to an instance of a class, which eventually can hold references to other instances of other classes.

So how does the garbage collector decide what to release?

If an instance can’t be reached with a path of references starting from the root, then that instance (or node) is garbage as it is unreachable.

What about circular dependencies? Well, if instance A references instance B and vice-versa, but there is no path from the root referencing either of them, then both considered garbage.

Find out more about Java memory management here.

Java’s GC memory tree diagram

Profiling memory on Android

Android’s architecture does a great job of isolating an app’s memory. Since apps run on different virtual machines, when an app is killed, all memory associated with it is freed (including leaked memory). The catch? It’s up to you to handle your app’s memory while it’s running.

Generally speaking, your app shouldn’t consume more than 256MB of RAM, which is the most common limit on current devices. If it does, it will fail to allocate and exit with an “Out of memory Exception.”

You should aim for a much lower usage of memory on non-intensive views. Especially when the app is in the background, to prevent Android from killing your app to free resources too quickly. On phones with 1 or 2GB of RAM, this can happen as soon as your app is sent to the background, and will result in a slower user experience.

So how do we find memory issues on our apps? Luckily, Android Studio has a very useful tool to do this: the Monitors.

These are useful not only to profile the memory usage but also CPU, Network, and GPU performance.

The first symptom you will notice if your app has memory leaks, is that your memory graph tends to increase while navigating and does not decrease while going back to your main view or putting the app in the background.

There are three buttons on the Memory Monitor that you will find extremely useful: force garbage collection, memory dump, and allocation tracking.

Force garbage collection helps find out if the memory was not released due to the GC being lazy. The other two help find out where the memory leaks are occurring.

The allocation tracking gives you information about where the memory is being assigned and helpfully displays percentages to rate them.

Allocation Tracking result

For example, my app is allocating around 82% of the memory for Bitmaps it loads from the assets directory. Does this mean that the memory is being leaked there? Not necessarily: it may be getting deallocated later.

This is where the memory dump comes in. After navigating my app, if I return to my initial view on the flow, I would expect the bitmaps to eventually be released. The dump file is a snapshot of the app’s memory heap at a given point.

Heap Dump File

Note: Most of the memory is being referenced by FinalizerReference class, this is fine as these are the GC references to your instances.

The second class on the list is byte[]. If we inspect the byte[] class we can detect that the largest instance (ordered by dominating size) actually comes from Bitmap references.

Common leaks

From my experience, there are three common code patterns that tend to lead to memory bleeding.

  1. Bitmaps. Almost every image on your app (including logo, icons, and all your used image resources) will have a Bitmap instance holding the pixel information. My recommendation is that if you manually create a Bitmap reference, don’t forget to call recycle() after you are done using it. This is especially true if you do a lot of Bitmap allocations, because they will not get released quick enough (sometimes you will have to wait until your app is sent to background).
  2. Anonymous Class leak. Take a look at the following code:

The code is supposed to run a recurring task every 5 seconds. It works, however, there is a problem: since it’s recurrent, and on each call it’s anonymously instantiating a Runnable, the memory of those instances is never released. Why? Because in Java, every anonymous class is non-static, which means it holds a reference to the class instantiating it.

This often happens by anonymously instantiating listeners and other interfaces among your app code. So be careful! If you instantiate a listener on your Fragment, and that listener is being referenced somewhere else, then your Fragment will not be released, and neither will the container Activity.

A solution, in this case, is to either avoid an anonymous class or at least move the instantiation out of the recurrent code, which will also reduce memory allocation and CPU usage:

3. Context instances. A lot of methods, classes and code snippets on Android require a context to work with. The thing is, there are many classes which implement the Context interface (the Application class, Activity, Service, etc).

Sometimes, the context will be kept referenced (often seen on singleton patterns like android system services). If the context you pass is an Activity, then your Activity will not be released even after it’s destroyed, and neither will any of it’s referenced fragments and other instances.

To be on the safe side, use “context.getApplicationContext()” unless the specific context is really needed.

Square to the rescue

I’ve lost count of the situations where Square libraries have helped me while developing Android applications. And memory leaks are not the exception. There is a library called LeakCanary which runs along your app on the device, dumps memory and checks whether memory is being leaked. If that is the case, it lets you know with a notification and a useful trace of the reference path.

LeakCanary trace

Note: Sometimes it’s a little invasive on the user experience (it freezes the app to dump memory), so make sure you only enable it on a debug environment and when needed.

Now there is no excuse to keep avoiding any performance issues with your app. It doesn’t matter if you do it manually, with the Android Monitors or with the help of a library, identifying the leaks and fixing them will not only make you a better developer but it will also improve the user experience, especially on low-end phones with limited memory.

--

--

Federico Ramundo
Wolox

Technical Leader @Wolox. Passionate about technology, video games, VR & AR.