How to avoid memory leak — Android and JVM languages

What is a memory leak

A Memory Leak happens when there are objects present in the heap that is no longer used, but the garbage collector(GC) is unable to remove them from memory. The reason that the garbage collector cannot remove them is that they are still referenced by other used objects, even they are unused already. Improper referencing may cause a memory leak.

Categories of Android memory leak

The reason for memory leaks in Android can be divided into 2 categories. As long as you can take care of these 2 categories of issues, you can safely avoid memory leaks, so you don’t need to remember 8 or even 10 different reasons.

  1. Longer lifecycle components reference shorter lifecycle components
  2. Resources should be closed, but not closed

1. Longer lifecycle components reference shorter lifecycle components

Static Variables

In the Java world, static fields have a life that usually matches the entire lifecycle of the running application, unless you set it to null purposely. Try not to hold any static fields inside your application class, activity or fragment, unless you know how it works.

Unregistered Listeners

Another example of a memory leak is an unregistered listener. If a registered listener is not unregistered, a memory leak could happen. But does that mean every listener we used, we have to unregister them? Apparently no. When we use setOnClickListener() for buttons inside an activity, normally we won’t unset the listener. Why? Because buttons have the same lifecycle as the activity, when the activity is destroyed, all the views inside the activity will be destroyed also.

Inner nested class and Anonymous class

In Java, any instance of an inner class contains an implicit reference to its outer class, the same as an anonymous class. Because the reference is hidden, we often ignore the issue. Let’s take a look at a common Java memory leak example.

public class MemoryLeakFactory {    // This is static in Kotlin
public class Leak {
}}

Handler

If we create an Handler by using an inner class, it will have the same situation. The Runnable object will implicitly reference the Activity it was declared in and will then be posted as a Message on the Handler’s MessageQueue. As long as the message hasn’t been handled before the Activity is destroyed, the chain of references will keep the Activity live in memory and will cause a leak.

System Services

System services are the bridges between system libraries/hardware layer and our application.

mSensorManager = (SensorManager)getContext().getSystemService(SENSOR_SERVICE);mSensorManager = (SensorManager)getApplicationContext().getSystemService(SENSOR_SERVICE);
  • If the system service is related to UI, use the activity context. For example, LayoutInflator
  • If the system service is directly or indirectly using the hardware in the device, the application context should be used to avoid memory leaks. For example, LocationManager, FingerprintManager, ConnectivityManager, AlarmManager, CameraManager
  • If LeakCanary detected a memory leak as the above example, try to use the application context.

Circular Reference/Retain Cycle

When object A holds the reference of object B, and object B at the same time hold the reference of object A, in this case, we created a circular reference or retain cycle.

1. Conclusion

When an object needs to hold a reference of another object, be careful of their lifecycles. If their lifecycles are not identical, please make sure the holder object has a shorter lifecycle, otherwise memory leak could happen. For Android, you need to understand the lifecycles of those components in the above diagram, and don’t let the longer life component hold reference of the shorter one directly.

2. Resources should be closed, but not closed

Sometimes, it’s inevitable to use the reference to longer life components. For example, inside the activity or view model, we need to hold the reference of a background thread for API request. In this case, we can limit the background thread to not live longer than the activity or view model.

Kotlin Coroutine

To be able to cancel the Kotlin coroutine, you have to initialize the CoroutineScope with aJob.

CompositeDisposable

Same as Kotin Coroutine, when the view model is destroyed, dispose() of CompositeDisposable should be called also.

AsyncTask

AsyncTask is not a good design, so it’s not commonly used nowadays, but it could be still used in some old projects. We cannot easily cancel or dispose an AsyncTask. There is a cancel method for AyncTask, but it doesn’t stop the background task immediately, you still need to check if the task is canceled manually by calling isCancelled() inside the doInBackground(), and exit return from doInBackground() directly once the task is canceled.

2. Conclusion

Whenever we make a new connection or open a stream, the JVM allocates memory for these resources, we need to close them when they are not used anymore. A few other examples include database connections, input streams, and session objects.

Conclusion

This article explained the memory leak from a perspective of the Android components lifecycle. Hopefully, after reading, you know why a memory leak could happen and how to avoid it.

Thanks for reading.

If you enjoyed this article, feel free to hit that clap button 👏 to help others find it.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store