Catch Leak If You Can!

Paulina Szklarska
7 min readJun 17, 2017

Memory leaks in Android are not so common topic. There are more sexy things to talk about - RxJava, Kotlin, Android O, the newest Google I/O. But in this world of libraries, making of all the job for us, do we still remember of basics?

A small leak will sink a great ship

~ Benjamin Franklin

Paraphrasing Benjamin Franklin words, a small (memory) leak will sink a great app. Imagine your application, very big, beautiful and full of users. And one day you see this:

java.lang.OutOfMemoryError: Failed to allocate a 26982127 byte allocation with 42642157 free bytes and 14MB until OOM

And your first thought (and probably first 10 responses on StackOverflow) may be to increase the amount of memory by adding a special flag to Android Manifest:

android:largeHeap=”true”

But wait! You probably don’t need this flag, accordingly to the documentation:

Most apps should not need this and should instead focus on reducing their overall memory usage for improved performance. Enabling this also does not guarantee a fixed increase in available memory, because some devices are constrained by their total available memory.

And probably this OOM error is caused not by your application’s high memory requirements, but by the memory leak. From now, you can suspect you have one in your application. But what does it mean to you?

#1 What is a memory leak?

In Java, every time we create some new object (e.g. by calling String text = “Hello World” ) it’s placed in the memory. Our application has a limited amount of memory, so there must be a mechanism for freeing memory used by no longer needed objects. This is where Garbage Collector enters. It’s his job to check if the object is unused and free this memory. There are many ways for GC to check this, and different languages use different algorithms. About GC mechanism in Android, you can read here: How Garbage Collection Works in Android Dalvik VM? From time to time GC searches the memory, checks if every object has any strong reference and if it doesn’t — removes it and frees some part of the memory. It happens on the system request (although we can also run GC from the code), and you can see when it happens in Android logs:

07-03 08:21:47.494: D/dalvikvm(258): GC_FOR_ALLOC freed 23K, 5% free 7073K/7431K, paused 75ms

When GC event occurs, the UI rendering is stopped. It’s no big deal if GC event pauses UI thread for less than 16 ms, but any longer will be visible to the user. That’s why it’s important to take care of memory management by ourselves.

Very, very, very simplified mechanism of memory management in Java

When GC finds an object which is not used anymore, he can remove this object from the memory. But what if GC finds an object, which is used in his definition, but in fact, we lost the reference to this object? Is it possible?

#2 What may cause memory leak?

In general, when GC wants to remove some object from the memory, he checks if there’s any strong reference to this object. So as long as there’s any strong reference, an object can’t be removed. Let’s look at the example:

String hello = "Hello";
String helloWorld = hello + " World";

In this simple example, helloWorld holds a reference to hello, so hello can’t be removed from the memory. But helloWorld is not used, so GC will free memory taken by it. Easy. But what about Android?

In general, the most dangerous memory leaks are caused when the reference to Activity is kept by something that may live longer than Activity. Because Context is something that may be easily leaked and Activity usually holds a reference to many large resources (e.g. views, images), so this is a big source of many OOM errors. But how Activity can live longer than it should?

#2.1 Static context

Let’s look at the example below. We’d like to have a static method for showing Toast , but we need to pass Context every time. So we make life easier and hold the reference to the Context in the static field.

An example of memory leak with static context

What happens when you pass Context as a static variable? Anything static is loaded by ClassLoader and kept in the memory as long as the application lives. It means that our Context will also be kept in the memory for so long. This could be Context from our Activity, so our whole Activity (with views, images, and anything else) could be in the memory for the whole time. Pretty bad, isn’t it? My advice is:

  • don’t use static Context at all
  • if you have to, use Application’s Context

#2.2 Inner Classes

Let’s look at the next example:

An example of memory leak with inner class

We have an inner class inside activity. In this inner class, we make some action delayed by 10 minutes. How long does the activity outside inner class lives? At least as long as the inner class. This is caused by a fact that inner class has a full access to the top-level class, including fields and methods (and context!). So what will happen if we run Handler delayed by 10 minutes inside inner class and close the InnerClassLeakActivity? Well, as long as the Handler lives, InnerClass must also live, and InnerClassLeakActivity also. So we’ll have a memory leak for 10 minutes. It doesn’t sound very bad, but imagine you have some repeating task in that Handler and your activity has a lot of heavy resources (images). This may end pretty bad.

What would be a fix for that? Using static nested class. It has its own lifecycle, independent from top-level class and thus doesn’t cause potential memory leaks. To summarize:

  • don’t make non-static nested classes that may live longer than Activity
  • try to replace non-static nested class with inner class

#2.3 Anonymous classes

This case is very similar to the case with inner class. Here also we have a class, not inner, but anonymous:

An example of memory leak with anonymous class

In this scenario we use AsyncTask for some long-running operation and we do use AT in activity, as an anonymous class. This is the same situation as previous — an anonymous class can live only if outer class lives, so as long as AsyncTask is not completed, anonymous class must live and AnonymousClassLeakActivity also. Not good.

#2.4 System services

What do we have here? We register listener for SensorManager and… we forgot to unregister this when activity is finished. What happens? SensorManager needs Context to register and we give him the activity’s context. So as long as the SensorManager lives (and it’s pretty long), so long the SensorManagerLeakActivity lives. My advice is to:

  • always remember to unregister all active listeners when they’re unneeded

#2.5 RxJava

The same occurs to RxJava. In the world of RxEverything it’s easy to create many subscriptions, but we also need to remember about taking care of them when they’re unneeded.

In this example, we create some Subscription to get all the Pokemons from API. We want this observable to repeat every 30 minutes (even if it’s not the best idea). And we don’t do anything after activity finishes its work. So every 30 minutes subscription will get the data, even if activity’s been closed for a while. So always remember to:

  • unsubscribe from all the subscriptions when they’re unneeded

#3 How to avoid memory leaks?

Besides the ways described above, sometimes there’s a need to hold a reference to the context at some place in the app. How to do it safely? Usually, when we create a reference to some object, we do it this way:

ImageView imageView = new ImageView(this);

This reference, by default, is strong. It means that this object is protected from being garbage collected. If there’s at least one strong reference to some object, GC won’t be able to release memory taken by it and it can lead to a memory leak. What other solutions do we have?

#3.1 Weak reference

A weak reference is a special kind of reference. It’s a reference that’s not strong enough to keep an object in memory upon garbage collecting. It means that if we’ll keep an object with only weak references, after first GC run, they’ll be garbage collected. Let’s look at the example of the previous bad-implemented anonymous class. Now it’s fixed:

We moved anonymous class to static nested class. Previously we could use context in this class (because anonymous classes, like inner classes, have an access to the fields and methods from top-level class). Now we want to have the same behavior, and we can do it by wrapping context in WeakReference:

WeakReference<Context> = new WeakReference(context);

WeakReference holds a reference to the Context, but it’s completely safe to use — whenever GC will need a memory and run, he’ll remove all the weak references and then Context will be removed from the memory. This feature has one disadvantage — we can’t assume that the object will be still available under the reference, so every time we want to use it, we need to check if it’s not null.

#3.2 Soft reference

There’s also another kind of reference — soft reference. You can create it in a very similar way:

SoftReference<Context> = new SoftReference(context);

The main difference between weak and soft reference is that the second one could not be so easily removed. GC would remove soft references only if there are no other options to release a memory. So, in theory, an object held by soft reference would remain in the memory for a longer time.

Conclusion

That’s it for now! From this post, you should know what is a memory leak and how to create leaks in many ways in your application. In the next article, I’ll write about tools which can be used for detecting and repairing memory leaks in our applications. Stay tuned!

--

--

Paulina Szklarska

Flutter GDE / Flutter & Android Developer / blogger / speaker / cat owner / travel enthusiast