A Troublesome Legacy: Memory Leaks in Java
Here's a solid primer for memory leaks in Java, including how they get started and how to avoid them, with a particular focus on Android development.
Join the DZone community and get the full member experience.
Join For FreeI once heard a colleague of mine make the following statement at a conference:
If you are an Android developer and you do not use WeakReferences, you have a problem.
No doubt one could argue whether WeakReferences are that relevant or not, but underneath them is one of the biggest problems in the Java world nowadays. Let’s have a walk in the world of the Memory Leak, and come back to the topic of the different types of references later.
Memory Leaks
Memory leaks are a silent killer. They can start small and live during an initial incubation time without anybody realizing it. With time, they keep growing, piling up, and accumulating. When you realize they’re there, it’s already too late: your entire code base is scattered with memory leaks, and finding a solution takes an enormous amount of effort. Therefore, it is a good investment to learn how this happens at an early stage in your career.
Let’s start from the beginning.
What Is a Memory Leak?
A memory leak happens when an object that is no longer used is still referenced in-memory by another object. It is particularly troublesome in the Android world, since Android devices have a very limited amount of memory, sometimes as little as 16 MB. As much as you may think this is enough to run an application, believe me: you can run over this limit rather quickly.
Eating up the available memory is the most direct result, but there’s another interesting side effect of running low on memory: the Garbage Collector (GC) will start triggering more frequently. When the GC triggers, the world stops. An app needs to render a frame every 16 milliseconds, and with the Garbage Collector running, this framerate can be compromised.
How Do Leaks Happen?
I have been on both sides of the fence in an interview for an Android app development job. You feel more secure when you have a strong candidate in front of you, rather than the other way around. I have been asked how to prevent memory leaks in the real world across several interviews. That made me think: would it not be a more interesting conversation to talk about how you can create a memory leak? It can also better prove a developer’s theoretical knowledge on the subject. Let’s see how we could provoke a few memory leaks:
Do not close an open stream. We typically open streams to connect to database pools, to open network connections, or to start reading files. Not closing them creates memory leaks.
Use static fields for holding references. A static object is always in memory. If you declare too many static fields for holding references to objects, this will create memory leaks. The bigger the object, the bigger the memory leak.
Use a HashSet that is using an incorrect hashCode() (or not using one at all) or equals(). That way, the HashSet will start increasing in size, and objects will be inserted as duplicates! Actually, when I was asked in interviews “what is the purpose of hasCode() and equals() in Hash Sets” I always had the same answer: to avoid memory leaks! Maybe not the most pragmatic answer, but it’s equally true.
If you are an Android developer, the possibility of memory leaks increases exponentially. The object Context is mainly used to access and load different resources, and it is passed to many classes and methods as a parameter.
Imagine the case of a rotating screen. In this scenario, Android destroys the current Activity, and tries to recreate the same state before the rotation happened. In many cases, if let’s say you do not want to reload a long Bitmap, you will keep a static reference that avoids the Bitmap being reloaded. The problem is that this Bitmap is generally instantiated in a Drawable, which ultimately is also linking with other elements, and gets chained to the Context level, leaking the entire class. This is one of the reasons why one should be very careful with static classes.
How Can We Avoid Memory Leaks?
Remember how we previously talked about the WeakReference? Let’s take a look at the different types of references available in Java:
Normal: This is the main type of reference. It corresponds to the simple creation of an object, and this object will be collected when it will no longer be used and referenced. It’s just the classical object instantiation: SampleObject sampleObject = new SampleObject();
Soft: This is a reference that’s not strong enough to keep an object in memory when a garbage collection event is triggered, so it can be null any time during execution. Using this reference, the garbage collector decides when to free the object memory based on the demand of the system. To use it, just create a SoftReference object passing the real object as a parameter in the constructor, and call the SoftReference.get() to get the object: SoftReference<SampleObject> sampleObjectSoftRef = new SoftReference<SampleObject>(new SampleObject()); SampleObject sampleObject = sampleObjectSoftRef.get();
Weak: This is like SoftReferences, but weaker.
Phantom: This is the weakest reference; the object is eligible for finalization. This kind of reference is rarely used and the PhantomReference.get() method always returns null. This is for reference queues that don’t interest us at the moment, but it’s useful to know that this kind of reference is also provided.
These classes may be useful if we know which objects have a lower priority and can be collected without causing problems in the normal execution of our application. Let’s see how to use them:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyAsyncTask().execute();
}
private class MyAsyncTask extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new MyObject();
}
}
}
Non-static inner classes are largely used in Android because they allow us to access outer classes’ IDs without passing their references directly. However, Android developers will often add inner classes to save time, unaware of the effects on memory performance. A simple AsyncTask is created and executed when the Activity is started. But the inner class needs to have access to the outer class, so memory leaks occur every time the Activity is destroyed, but the AsyncTask is still working. This happens not only when the Activity.finish() method is called, but even when the Activity is destroyed forcibly by the system for configuration changes or memory needs, and then it’s created again. AsyncTask holds a reference to every Activity, making it unavailable for garbage collection when it’s destroyed.
Think about what happens if the user rotates the device while the task is running: the whole instance of Activity needs to be available all the time until AsyncTask completes. Moreover, most of the time we want AsyncTask to put the result on the screen using the AsyncTask.onPostExecute() method. This could lead to crashes because the Activity is destroyed while the task is still working and views references may be null.
So what is the solution to this? If we set the inner class as a static one, we cannot access the outer one, so we need to provide the reference to that. In order to increase the separation between the two instances and let the garbage collector work properly with the Activity, let’s use a weaker reference to achieve cleaner memory management. The previous code is changed to the following:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new MyAsyncTask(this).execute();
}
private static class MyAsyncTask extends AsyncTask {
private WeakReference < MainActivity > mainActivity;
public MyAsyncTask(MainActivity mainActivity) {
this.mainActivity = new
WeakReference < > (mainActivity);
}
@Override
protected Object doInBackground(Object[] params) {
return doSomeStuff();
}
private Object doSomeStuff() {
//do something to get result
return new Object();
}
@Override
protected void onPostExecute(Object object) {
super.onPostExecute(object);
if (mainActivity.get() != null) {
//adapt contents
}
}
}
}
This way, the classes are separated and the Activity can be collected as soon as it’s no longer used, and the AsyncTask object won’t find the Activity instance inside the WeakReference object and won’t execute the AsyncTask.onPostExecute() method code.
Together with using References properly, we can use these methods to avoid provoking memory leaks in our code:
Avoid using non-static inner classes in your Activities, use a static inner class and make a WeakReference.
When you have the option to use Context, try using Activity Context instead of Application Context.
In general, never keep long-term references to any kind of Context.
Opinions expressed by DZone contributors are their own.
Comments