Memory Leaks in details in Android

Avoid memory leaks and make a super-high-quality, memory-efficient Android app.

SHISHIR
PROGRAMMING LITE
10 min readJan 11, 2020

--

Most of the time, we get very little time for our project. That’s why we focus in building features, functionalities and the UI components of our apps. We forget to focus on the core issue like performance and quality of the app which is the major part of an application. In this article, we will focus on one of the most important optimization techniques in android: Memory leaks.

The concept of memory leaks is quite daunting for a lot of developers out there. Most of the developers find it 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.

There are a lot of articles on memory leaks and how to fix them. But when I was learning myself, I found that none of them were in one place and it was hard to keep track of it all. So I thought I would collectively post an article on it so that it might help people in the future.

So let’s begin.

How memory works in android?

RAM (Random Access Memory)

Most apps on Android run on top of Android Runtime (ART), which replaced the deprecated Dalvik Virtual Machine(DVM). ART and Dalvik are similar to Java Virtual Machine (JVM) and both of them use two separate memory spaces to store running application and their data — Stack and Heap. Stack and Heap are two major parts of RAM (RAM stands for Random Access Memory is the memory in android devices/computers). Let’s discuss details about Stack & Heap.

# Stack memory

  • The Stack is used for static memory allocation. It is used to store local variables (primitive types and references to objects).
  • Stack memory is always referenced in a LIFO (last in, first out) fashion.
  • Stack memory is relatively small compared to heap memory.
  • The Java stack size on Dalvik is usually 32 KB for Java code and 1 MB for native (C++/JNI) code. ART introduced a unified stack for both Java and C++ that is around 1 MB.
  • When an app hits the stack memory limit, StackOverflowError is emitted.
  • Whenever a method call is made, a new block (stack frame) with the method’s local variables is pushed to the stack. When the method completes, its stack frame is popped from the stack and any possible result value is pushed back onto the stack.

# Heap Memory

  • The Heap is used for dynamic memory allocation.
  • To provide a smooth user experience, Android sets a hard limit on the heap size for each running application. The heap size limit varies among devices and is based on how much RAM a device has.
  • On Android has a maximum heap size limit (varies for each device) marked as ‘largeHeap’ for every application. You can check the maximum heap size available for your application by calling getMemoryClass() API of ActivityManager service. Most devices running Android 2.3 or later will return this size as 24MB or higher but is limited to 36 MB (depending on the specific device configuration).
  • If your app hits this heap limit and tries to allocate more memory, it will receive an OutOfMemoryError and will terminate.
  • Heap memory is used to allocate objects. Whenever you create an object, it’s always created in the heap.
  • The heap is different from the stack, so the objects will not be reclaimed automatically when the function is done.
  • Virtual machines, like JVM (Java Virtual Machine), DVM (Dalvik Virtual Machine) or ART (Android Runtime) has a superhero we called it Garbage Collector who gonna care about detect and reclaimed those unused objects to get more space in the memory.
  • The garbage collector is looking for the unreachable objects, in other words, if there is an object in the heap that doesn’t contain any reference to it, it will be released.
Garbage Collector
  • Sometimes it happens that there are unused objects in the heap but still being referenced from the stack. So the Garbage Collector fails to release them from the heap. So the memory size get increased. This is called memory leak.

So What is memory Leaks?

failure of releasing unused objects from the Heap memory

Why we should care about memory leak?

A memory leak happens when memory is allocated but never freed. This means the garbage collector is not able to take out the trash.

So, when the user keeps on using our app, 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 they don’t slow down your app significantly (2ms to 5ms pause). But remember, the less the garbage collector has to run, the better your app’s performance will be.

If your app has some serious memory leaks hidden under the hood, these short GCs will not be able to reclaim the memory, and the heap will keep on increasing, which will force a larger GC to kick off. This larger GC, called a “stop-the-world” GC, pauses the entire application main thread for around 50ms to 100ms. At this point, your app seriously lags and becomes almost unusable.

If this doesn’t fix the problem, then 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, leading to the dreaded OutOfMemoryError, which crashes your app.

When you know the impact memory leaks can have on your app, you understand why you need to address them immediately.

How you will detect memory leak?

Now that you know that you need to fix memory leaks hidden inside your app, how will you actually detect them?

  • The good thing is that Android Studio has a very useful and powerful tool for this, Android Profiler which replaces the previous Android Monitor tools and comes with Android Studio 3.0 and later. There are monitors not only for memory usage, but for network, CPU, and GPU usage as well.
  • Also there is an awesome library called LeakyCanary that is great for finding out the leaks in our app along with the stack trace.

Common memory leak scenarios in android and how to fix them

In my experience, these are some of the most common scenarios that can lead to memory leaks.

# Contexts

Common reason for memory leaks in Android is the misuse of the Context instances. 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.

Example 01: Singleton Class Reference

When you initialize the singleton from an activity, you pass a long-lived context-activity reference to a singleton like below

Then the singleton will hold the activity until the end of the application.

Solution 01: Pass application-context instead of activity-context.

Solution 02: Update singleton class to use the application context when creating the object instead of using getting context from outside.

Solution 03: If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the singleton class is set to null.

Static Activity or View Reference:

Consider the below example — You are declaring a TextView as static (for whatever reason). If you reference an activity or view directly or indirectly from a static reference, the activity would not be garbage collected after it is destroyed.

Solution: Always remember to NEVER use static variables for views or activities or contexts.

# 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.

Example 01: LocationManager Reference

Suppose you want to receive location updates in your app. So we need to get the LocationManager system service and register a listener for location updates.

Here we implement the location interface in the activity itself, meaning that the LocationManager will hold a strong reference to our activity. Now when it’s 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, resulting in memory being leaked.

Solution: Just unregister the listener in the onDestroy() method and you are good to go.

Example 02: Broadcast receiver Reference

Suppose we need to register a local broadcast receiver in your activity. But if we don’t unregister the broadcast receiver, then it still holds a reference to the activity, even if you close the activity.

Solution: Always remember to call unregister receiver in onStop() of the activity.

# 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.

Example 01: AsyncTask Reference

Memory-leak with inner class

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?

But this will generate a leak if you leave the activity and the task didn’t finish as the non-static inner class holds an implicit reference to the outer enclosing class (that is, the Activity itself).

Solution:

  1. 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.
  2. Pass required objects references to the inner class through its constructor as static class can’t accept the non-static variables (like the TextView) of the outer class.
  3. Cancel the async task in onDestory().
  4. Need one more step. Don’t forget to wrap these object references in a WeakReference to prevent further memory leaks as our reference to the textView is still strong and has the ability to keep the instance alive preventing garbage collection.

So the solution is:

Notice that in onPostExecute we have to check for null to verify, if an instance has been reclaimed or not.

# Anonymous classes

Anonymous classes are a favorite tool among developers. In android, every developer uses Anonymous Class (Runnable) at least once in a project. But anonymous classes are nothing but non-static inner classes which can cause potential memory leaks just because of the same reason we talked about before.

Example 01: Handler Reference

Uses handler and Anonymous Runnable class. The memory will be leak when we quit the activity before the Runnable is finished as Any Anonymous Class has a reference to its parent (activity).

Example 02: Retrofit Reference

Here, we are using a very popular library called Retrofit, which is used 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 the Activity is rotated or destroyed somehow before the call ends, then the entire Activity instance will be leaked.

Solution:

  1. Don’t do any long operating with Anonymous Class or we need a Static class for it and pass WeakReference into it (such as activity, view...). Thread is the same with Anonymous Class.
  2. Cancel the Handler, Timer, Network Call when activity is destroyed.

Request Large Heap for your app

You can’t increase the heap size dynamically. But one immediate solution for dealing with large memory requirements is to request a large Dalvik heap for your app. You can do this by adding android:largeHeap="true" to your <application> tag in AndroidManifest.xml. But this will not work on any pre Honeycomb devices and there is no guarantee how large the large heap will be.

Attention !

It is strongly advised that you don’t use a large heap just to allow higher memory usage. You should always optimize your memory usage, because -

Firstly, a large heap on low-memory, low-end devices can still be too small for your application.

Secondly, using this may adversely affect your app performance. Because you are telling system to increase the maximum heap limit. When this happens it will take more time for garbage collection. If you check the log you can see that GC Pause time will be greater. Ideally it should be between 2–5ms.In this case it can vary even up-to 30–45ms. So don’t set large heap property to true just because you are getting out of memory. Use it as the very last step.Otherwise it will be a performance hit.

So basically to summarize to avoid memory leaks:

  • Use applicationContext() instead of activity context when possible. If you really have to use activity context, then when the activity is destroyed, ensure that the context you passed to the class is set to null.
  • Always use applicationContext() to create a singleton object and toast message for your application.
  • Also you can use applicationContext() for starting any service.
  • Make sure to unregister broadcast receivers once their job it’s finished. A good practice is to register inside onResume() method and unregister inside onPause() method
  • Cancel any asyncTasks, Timer or Threads inside onDestroy().
  • Avoid non-static inner classes in an activity. If really need use static inner class and make a weak reference to the activity inside.
  • Never use static variables to declare views or activity context.
  • Be careful with the usage of static variables, remember to set them to null once are no longer useful to the application, that way they would be eligible to be garbage collected.
  • Always use a weakReference of the activity or view when needed.(There is no need for explicit nulling when using WeakReference)
  • Use Android Profiler and LeakCanary to detect memory leaks.

Conclusion

Now we know how memory works in android, what the memory leaks, why we need to avoid them and how we will detect and avoid them.

So 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.

Happy Coding :)

--

--

SHISHIR
PROGRAMMING LITE

{ 'designation' : 'Lead Software Engineer' , 'hobby' : [ 'Music', 'Photography', 'Travelling' ] ,’email’: ‘shishirthedev@gmail.com’ }