Memory leak in Android

Zhang QiChuan
4 min readFeb 8, 2017

--

If Java is your first programming language, most likely you will take memory management for granted and let the built-in JVM Garbage Collector from JVM to do the job. It is probably doing fine in most cases, however there are some scenarios where unexpected memory leak could happen.

How does Garbage Collector determine whether the memory of a object can be freed? If a object has no other references point it, then it is marked as a candidate to be Garbage Collected. When a object is not used anymore in the program but its memory cannot be released by the Garbage Collector, it is considered a memory leak, and here are some common memory leaks in Android.

Static context object

The lifecycle of a static object starts when the enclosing class is loaded, and ends when the enclosing class is unloaded.

public class MyActivity extends AppCompatActivity {

private static Context sContext;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sContext = this;
}
}

When Android OS decides to destroy MyActivity and free up the memory, the Garbage Collector cannot free the memory of this MyActivity instance because there is a static sContext object references to the MyActivity instance, and the memory of this static sContext object cannot be freed until this MyActivity class is unloaded.

There are a few solutions to this problem, we can either set sContext object to null in the onDestroy() callback, avoid using static Context object at all, or use WeakReference.


public class MyActivity extends AppCompatActivity {

private static WeakReference<Context> sContextReference;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
sContextReference = new WeakReference<>(this);
}
}

Static view object

A view object holds a reference to the activity that it is housed in, so having a static view object is equivalent to having a static context object.

Non-static anonymous inner class

The tricky part of this issue is that a instance of non-static anonymous inner class has a implicit reference to the instance of its enclosing class, let us take a look at the code snippet below.

public class MyActivity extends AppCompatActivity {

private TextView resultTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultTextView = (TextView)
findViewById(R.id.txt_result);
// Start an asyncTask
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground( final Void ... params ) {
// a methods takes very long time
String result = aMethodTakesVeryLongTime();
return result;
}

@Override
protected void onPostExecute( final String result ) {
// continue what you are doing...
resultTextView.setText(result);
}
}.execute();
}}

The above code is very common in Android development. AsyncTask is one of the popular class for multi-threading operation, and using anonymous inner class is a convenient way to create a instance of interface without creating a subclass, and the anonymous inner class has access to all the private members of the outer class.

There is a edge case to consider when the doInBackground() takes a very long time, and the activity may be already finished before onPostExecute() callback is reached. However, at this point of time, the AsyncTask object has not yet finished its operation and thus not to be garbage collected, and it has the implicit reference to the finishing activity, which prevents the memory of this activity to be released, and therefore causes a memory leak.

To solve this issue, we can use static inner class that does not maintain a strong reference to the instance of the outer class, and use WeakReference to reference outer class and access its private members.

public class MyActivity extends AppCompatActivity {

private TextView resultTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
resultTextView = (TextView)
findViewById(R.id.txt_result);
// Start an asyncTask
new MyTask(this).execute();
}private static class MyTask extends AsyncTask<Void, Void, String> { private final WeakReference<MyActivity> mActivityRef; public MyTask(MyActivity activity){
mActivityRef = new WeakReference<>(activity);
}

@Override
protected String doInBackground(final Void ... params) {
// a methods takes very long time
String result = aMethodTakesVeryLongTime();
return result;
}

@Override
protected void onPostExecute( final String result ) {
// continue what you are doing...
if (mActivityRef.get() != null){
mActivityRef.get().resultTextView.setText(result);
}
}
}

The application memory allocated to Android application varies from device to device, depends on the OS version and the device specification, it can range from minimum 16MB in low end devices to a few hundreds MB in high end phones. It is necessary to be cautious about memory management and use the best practise to avoid memory leaks. Fortunately there is a great tool from square called LeakCanary that helps developer to identify the potential memory leaks during debug run.

I hope you enjoyed this article and found it useful, if so please hit the Recommend button. Feel free to comment about your thoughts. Happy coding!

--

--

Zhang QiChuan

Software Engineer — Singapore