Everything You Need To Know About Memory Leaks In Android Apps

Aritra Roy
Aritra's Musings
Published in
13 min readJun 9, 2017

--

Making an Android app is easy, but making a super-high quality memory efficient Android app is not. From my personal experience, I used to focus and find a lot of interest in building new features, functionalities and the UI components of my apps.

I was mainly inclined towards working on something which had a more visual impact rather than spending time on something which no one would even notice at their first glance. I started forming a habit of avoiding or giving lesser-priority to app optimization related things (detecting and fixing memory leaks was one of them).

This naturally led to acquiring technical debt which started affecting the performance and quality of my apps in the long run. I slowly tried changing my mindset and today I am a lot more “performance-focussed” than I was a year ago.

The concept of memory leaks is quite daunting for a lot of developers out there. They find is difficult, time-consuming, boring and unnecessary but fortunately, none of these are actually true. Once you start getting into it, you will certainly fall in love with it.

In this article, I would try to make this topic as simple as possible so that even the newbie developers can start building good quality and high performance Android apps right from the beginning of their career.

The Garbage Collector Is Your Friend, But Not Always

Java is a robust language. In Android, we are not writing code (sometimes we do) in languages like C or C++ where we have to manage the entire memory allocation and deallocation ourselves. Thankfully, Java knows how to clear its own mess itself.

Now the first question that came to my mind was that if Java has got a dedicated memory management system built-in which can automatically clear out the memory when not needed, then why am I even concerned about all this. Is the garbage collector faulty?

No, it certainly is not. The garbage collector works just as it should but it is our own programming mistakes that sometimes inhibit the garbage collector from collecting unnecessary chunks of memory which it could have otherwise.

So basically, it is our own fault which leads to all this mess. The garbage collector is one of the finest achievements of Java and it deserves its due respect.

Recommended Reading

A Tad Bit More On Garbage Collectors

Before going any further, you need to know a bit about how the garbage collector actually works. The concept is pretty simple but what goes under the hood is quite complicated sometimes. But don’t worry, we will focus on the simple stuff mostly.

Every Android (or Java) application have got a starting point from where objects start getting instantiated and methods get called. So we can consider this starting point as the “root” of the memory tree. Some objects keep a reference to the “root” directly and other objects are instantiated from them keeping a reference to these objects and so on.

Thus a chain of references is formed which creates the memory tree. So, the garbage collector starts from the GC roots and traverses the objects directly or indirectly linked to the roots. At the end of this process, there are some objects which have never been visited by the GC.

These are your garbage (or dead objects) and these are the ones which are eligible to be collected by our beloved garbage collector.

So far the story seems like a fairy tale but let’s dig deeper into it for the real fun to begin.

Bonus: If you want to learn more about garbage collectors, I highly recommend you to have a look at here and here.

So, What Is A Memory Leak Now?

Till now, you have got a brief idea about garbage collectors and how the memory management actually works for Android apps. Now let’s focus on the topic of memory leaks in greater detail.

In simple words, memory leaks happen when you hold on to an object long after its purpose have been served. The actual concept is as simple as that.

Every object has got its own lifetime after which it needs to say goodbye and leave the memory. But if some other object(s) is holding onto this object (directly or indirectly), then the garbage collector will not be able to collect it. And this, precisely my friend, is a memory leak.

But the good news is that you don’t need to worry too much about every single memory leak happening in your app. Not all memory leaks will hurt your app.

There are some leaks which are really minor (leaking a few kilobytes of memory) and there are some in the Android framework itself (yes, you read it right ;-)) which you can’t or don’t need to fix. These will generally have a minimum impact on your app’s performance and can be safely ignored.

But there are others which can crash your app, make it lag like hell and literally bring it down to its knees. These are the ones which you need to take care of.

Recommended Reading

Why Should You Really Care About Fixing Memory Leaks?

No one wants to use an app which is slow, laggy, eats up a lot of memory or crashes after a few minutes of use. It can create a really bad experience for the user and if it continues for long, then there is a huge possibility that you will lose that user forever.

As the user keeps on using your app, the heap memory also keeps on increasing and if there are memory leaks in your app then the unused memory from the heap cannot be freed up by the GC. So the heap memory of your app will constantly increase until it reaches a point of death where no more memory can be allocated to your app thereby leading to the dreaded OutOfMemoryError and ultimately crashing your app.

You also have to remember one thing that the garbage collection is a heavy process, so the less the garbage collector runs, the better for your app.

As the app is being used and the heap memory keeps on increasing, a short GC will kick off and try to clear up immediate dead objects. Now these short GCs run concurrently (on a separate thread) and doesn’t slow down your app (2–5 ms pause) at all.

But if your app have got some serious memory leaks hidden under the hood then these short GCs will not be able to reclaim the memory and the heap will keep on increasing thereby forcing a larger GC to kick off which will generally be a “stop-the-world” GC pausing the entire application main thread (for around 50–100 ms) thereby making your app seriously lag and sometimes almost unusable for some time.

So now you know the impact these memory leaks can have on your app and why you need to fix them immediately to give your users the best experience they deserve.

How Do You Detect These Memory Leaks?

By now, you should be quite convinced that you need to fix these memory leaks hidden inside your app. But how will you actually detect them?

The good thing is that Android Studio has got a very useful and powerful tool for this, the Monitors. There are individual monitors not only for memory usage but for network, CPU and GPU usage as well (more info here.)

While using and debugging your app, you should keep a close eye on this memory monitor. The first symptom of a memory leak is when the memory usage graph constantly increases as you keep using the app and never goes down, even when you put the app in background.

The Allocation Tracker comes in handy which you can use to check the percentage of memory allocated to different types of objects in your app. You can have a clear idea of which objects occupy the most memory and need to be addressed specifically.

But this itself is not enough as you now need to use the Dump Java Heap option to create a heap dump which actually represents the snapshot of the memory at a given point of time. Seems like a lot of boring and repetitive work, right? Yes, it truly is.

We engineers tend to be lazy and this is exactly where LeakCanary comes to the rescue. This library runs along with your app, dumps memory when needed, looks for potential memory leaks and gives you a notification with a clean and useful stack trace to find the root cause of the leak.

LeakCanary makes it super easy for anyone to detect leaks in their apps. I can’t thank Py (from Square) enough for making such an amazing and life-saving library. Kudos!

Bonus: If you want to learn in-detail about how to make the most of this library, check this out.

Recommended Reading

Some Really Common Memory Leak Scenarios And How to Fix Them

From my experience, these are some of the most common scenarios that can lead to memory leaks and it is very likely, that you will encounter these scenarios in your day-to-day Android development.

Once you know when, where and how these memory leaks can happen, you can go ahead and fix them without much difficulty.

Unregistered Listeners

There are many situations where you register a listener in your Activity (or Fragment) but forget to unregister it. This can easily lead to a huge memory leak if you are not too lucky. Generally, these listeners balance off each other, so if you register it somewhere you also need to unregister it right there.

Now let’s have a look into it with a simple example. Suppose you want to receive location updates in your app then all you need to do is get the LocationManager system service and register a listener for location updates.

private void registerLocationUpdates(){
mManager = (LocationManager) getSystemService(LOCATION_SERVICE);
mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
TimeUnit.MINUTES.toMillis(1),
100,
this);
}

You have implemented the listener interface in your Activity itself and thus the LocationManager keeps a reference to it. Now when its time for your Activity to die, the Android framework will call onDestroy() on it but the garbage collector will not be able to remove the instance from memory because the LocationManager is still holding a strong reference to it.

The solution is very simple. Just unregister the listener in onDestroy() method and you are good to go. This is what most of us forget or don’t even know.

@Override
public void onDestroy() {
super.onDestroy();
if (mManager != null) {
mManager.removeUpdates(this);
}
}

Inner Classes

Inner classes are very common in Java and are used by many Android developers for various tasks because of their simplicity. But with improper usage, these inner classes can also lead to potential memory leaks.

Let’s have a look into it with the help of a simple example again,

public class BadActivity extends Activity {    private TextView mMessageView;    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_bad_activity);
mMessageView = (TextView) findViewById(R.id.messageView);
new LongRunningTask().execute();
}
private class LongRunningTask extends AsyncTask<Void, Void, String> { @Override
protected String doInBackground(Void... params) {
return "Am finally done!";
}
@Override
protected void onPostExecute(String result) {
mMessageView.setText(result);
}
}
}

It is a very simple Activity which starts a long running task in a background thread (maybe a complex database query or a slow network call). After the task is finished, the result is shown in a TextView. Seems all good?

No, certainly not. The problem here is that the non-static inner class holds an implicit reference to the outer enclosing class (that is, the Activity itself). Now if we rotate the screen or if this long running task lives longer than the lifetime of the Activity, then it will not let the garbage collector collect the entire Activity instance from memory. A simple mistake leading to a huge memory leak.

But the solution again is very simple, just have a look at it and you will understand for yourself.

public class GoodActivity extends Activity {    private AsyncTask mLongRunningTask;
private TextView mMessageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_good_activity);
mMessageView = (TextView) findViewById(R.id.messageView);
mLongRunningTask = new LongRunningTask(mMessageView).execute();
}
@Override
protected void onDestroy() {
super.onDestroy();
mLongRunningTask.cancel(true);
}
private static class LongRunningTask extends AsyncTask<Void, Void, String> { private final WeakReference<TextView> messageViewReference; public LongRunningTask(TextView messageView) {
this.messageViewReference = new WeakReference<>(messageView);
}
@Override
protected String doInBackground(Void... params) {
String message = null;
if (!isCancelled()) {
message = "I am finally done!";
}
return message;
}
@Override
protected void onPostExecute(String result) {
TextView view = messageViewReference.get();
if (view != null) {
view.setText(result);
}
}
}
}

As you can see, we have transformed the non-static inner class to a static inner class as static inner classes don’t hold any implicit reference to its enclosing outer class. But now we can’t access the non-static variables (like the TextView) of the outer class from a static context so we have to pass our required objects references to the inner class through its constructor.

It is highly recommended to wrap these object references in a WeakReference to prevent further memory leaks. You need to start learning about the various types of references available in Java and how you can make the best use of them to avoid memory leaks.

Anonymous Classes

Anonymous classes are favorite among many developers because of the way they are defined which makes it really easy and concise to write your code using them. But from my experience, these anonymous classes have been the most common reason for memory leaks.

Anonymous classes are nothing but non-static inner classes which can cause potential memory leaks just because of the same reason I talked about before. You have been using it at several places in your app but didn’t knew that this, if wrongly used can have a severe impact on your app’s performance.

public class MoviesActivity extends Activity {    private TextView mNoOfMoviesThisWeek;    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_movies_activity);
mNoOfMoviesThisWeek = (TextView) findViewById(R.id.no_of_movies_text_view);
MoviesRepository repository = ((MoviesApp) getApplication()).getRepository();
repository.getMoviesThisWeek()
.enqueue(new Callback<List<Movie>>() {

@Override
public void onResponse(Call<List<Movie>> call,
Response<List<Movie>> response) {
int numberOfMovies = response.body().size();
mNoOfMoviesThisWeek.setText("No of movies this week: " + String.valueOf(numberOfMovies));
}
@Override
public void onFailure(Call<List<Movie>> call, Throwable t) {
// Oops.
}
});
}
}

Here, we are using a very popular library Retrofit for making a network call and displaying the result in a TextView. It is quite evident that the Callable object keeps a reference to the enclosing Activity class.

Now if this network call runs on a very slow connection and before the call ends, the Activity is rotated or destroyed somehow, then the entire Activity instance will be leaked.

It is always advisable to use static inner classes instead of anonymous classes whenever possible or necessary. It’s not that I am suddenly telling you to stop using anonymous classes completely, but you have to understand and judge when it is safe to use them and when not.

Recommended Reading

Bitmaps

Every image that you see in your app is nothing but Bitmap objects which contain the entire pixel data of an image.

Now these bitmaps objects are generally quite heavy and if dealt improperly can lead to significant memory leaks and can eventually crash your app due to OutOfMemoryError. The bitmap memory related to image resources that you use in your app are all automatically managed by the framework itself but if you are dealing with bitmaps manually, be sure to recycle() them after use.

You also have to lean how you can manage these bitmaps properly, load large bitmaps by scaling them down and use bitmap caching and pooling whenever possible to reduce memory usage. Here is a good resource to understand bitmap handling in-detail.

Contexts

Another quite common reason for memory leaks is the misuse of the context instances. The Context is simply an abstract class and there are many classes (like Activity, Application, Service, etc.) which extend it to provide their own functionalities.

If you want to get things done in Android, the Context object is your go-to-guy.

But there is a difference between these contexts. It is very important to understand the difference between the activity-level context and the application-level context and which one should be used under what circumstances.

Using the activity context in the wrong place can keep a reference to the entire activity and cause a potential memory leak. Here is an excellent article for you to start with.

Conclusion

Now you must have known how the garbage collector works, what memory leaks are and how they can have a significant impact on your app. You have also learned how these memory leaks can be detected and fixed.

No excuses anymore, let’s start building good quality, high-performance Android apps from now on. Detecting and fixing memory leaks will not only make your app’s user experience better but will slowly turn you into a better developer as well.

This article was originally published on TechBeacon.

--

--

Design-focused Engineer | Android Developer | Open-Source Enthusiast | Part-time Blogger | Catch him at https://about.me/aritra.roy