Identifying Memory Leaks in Android Applications
The Basics of Memory Management in Java and Android in Brief
In Java, memory is allocated only to objects. There is no explicit allocation of memory, only creation of new objects. Arrays for that matter are treated as objects.
The runtime environment employs a garbage collector (GC) that reclaims the memory occupied by an object, once it determines that an object is no longer accessible. All objects are created on the Dalvik VM’s memory heap, and the GC periodically disposes of objects that are eligible for garbage collection.
So what makes an object eligible for garbage collection? An object neither shall be reachable from any live threads, nor shall it have any static references to it. Cyclic references among objects, however, do not count, i.e. if you have a pool of objects which reference one another, and no other references, be it static or not, to that pool of objects, then the entire group is eligible for garbage collection.
This automatic process of garbage collection makes it safe to throw away unneeded object references because the GC does not collect objects still needed somewhere. Hence, you never run the risk of releasing memory prematurely.
On a side note, Android’s GC uses a mark-and-sweep algorithm to do its job. It allows the Dalvik VM to take advantage of the extra cores in your CPU to clean up dead memory on a background thread. This helps to drastically reduce the amount of time a program is forcefully paused when garbage collection runs.
At this point you may be wondering: “But if Java handles memory management so well, isn’t my program immune to memory leaks?” Well… it is not.
The Prime Suspects
Bitmaps are by far the most widespread cause for memory leakage in Android. If you are not careful, bitmaps can quickly consume your available memory budget leading to an application crash due to the dreaded exception: “java.lang.OutOfMemoryError: bitmap size exceeds VM budget”. Therefore, you should be really efficient when dealing with multiple image sets, large bitmaps, or animation. The out-of-memory error comes when the application crosses the heap limit, or your process demands an amount of memory that crosses the heap limit. This heap limit is different for different devices and also differs for phones and tablets. So to avoid this situation, do not pull large images into main memory but rather use the BitmapFactory.Options’ inSampleSize property of the bitmap to bring the right size to your screen. Here is a very good description and example of how this can be achieved: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html.
Java Collections like Lists, Maps, and Sets are other good candidates for memory leaks because they can accidentally retain references to unused objects. Having large collections of objects whose references are being tossed around in an application is a very good way to waste memory by keeping the references to those objects intentionally or unintentionally. So just be careful about how you handle such collections.
Last but not least, from the list of primary suspects for memory leaks are the Context-related memory leaks. On Android, to load and access resources as well as on many other occasions, a Context is required. All widgets receive a Context object in the constructor and hold a reference to the entire activity and everything else it is holding onto like the View hierarchy and its resources. There are two easy ways to avoid Context-related memory leaks.
First, avoid escaping the Context outside of its own scope, and be extra careful with inner classes and their implicit reference to the outer class. Do your best to avoid non-static inner classes in favor of static inner classes when you cannot control their life cycle. In cases where you need to reference the Activity, always make a weak reference to it, and if you do not do the latter, you should be certain that all references to an Activity have the same life cycle as the Activity itself.
Second, start using the Application context, which lives as long as your application is alive and does not depend on the Activity’s life-cycle. If you plan on keeping long-lived objects that need a Context, such as Drawable objects for example, remember the Application object. You can obtain it easily by calling Context.getApplicationContext() or Activity.getApplication().
Detecting a Memory Leak
The first step in reducing the amount of memory used by an application is to detect whether the application is consuming too much memory. Then analyze the content of the VM heap and identify the issue. Here follow some clues to guide you around.
Clue #1: Your application crashes with a java.lang.OutOfMemoryError after running for some time. It is true that you can get an out of memory error simply for attempting to use too much memory. However, if your application is running for a while, especially in lack of any user interaction with it, you can bet that the out of memory error that crashed your application is due to a memory leak.
Clue #2: You see frequent GC_* lines in logcat before the crash. This means that the garbage collector is trying really hard to free up some memory due to the increasing demand of your application. Obviously, despite the many runs, the GC does is not succeeding in this task, so you are risking to run out of memory at some point.
Identifying the Source of the Leak
Assuming that you know what the Eclipse Memory Analyzer Tool (MAT) is and can make your way around it, just keep on reading. For those who need to fill in their gaps, you can check out the following link for a start: https://www.eclipse.org/mat/.
Now that we have established that you are already familiar with the Eclipse MAT, here is what to do.
Step #1: Use DDMS (Dalvik Debug Monitor Server) to dump heap snapshots (an HPROF file).
Step #2: Use the Android SDK’s “hprof-conv” tool to convert the Android-specific HPROF file into a generic HPROF file that can be analyzed by Eclipse Memory Analyzer.
Step #3: Open the converted HPROF using Eclipse Memory Analyzer.
Step #4: Analyze the results using the “Histogram” view and the “Dominator Tree” view. The first one shows a list of all the classes currently used in the application. What you need to be looking at here is the values for the shallow heap, showing memory used by all instances, and the retained heap, showing the memory that would be released if we freed these objects. The dominator view shows a list of the biggest objects (in terms of their heap size), and what keeps them alive. The benefit of that is that it allows you to focus your analysis on the bigger objects and identify specific classes in a clearer way.
Step #5: Now unless it is too obvious, we need to create another HPROF snapshot and compare it to the first one in an attempt to identify which objects are responsible for a leak. So exercise your application in a way that tends to cause it to exhibit the memory leak, and then use DDMS to dump another heap snapshot.
Step #6: Use “hprof-conv” to convert the HPROF file as in step #2.
Step #7: Open both HPROF files in Eclipse Memory Analyzer and compare their histograms. Compare the number of instances and sizes of each type of object between the two snapshots. If you spot a sustained increase in the number of objects, this may indicate a leak! Keep in mind though that only you decide whether there is a problem or not in the way your application handles certain objects.
Fixing the Leak
The leak must be the result of unused objects still having references to them. So what you can do to fix that depends on the logic of your application and the structure of your code. The basic rule of thumb is:
- Stop creating so many objects; maybe there is an alternative, more optimized way to do the job;
- Stop maintaining references to the objects when you don’t need them;
- Use weak references (search for WeakHashMap on the Internet for example), so that those references are invalidated when memory becomes really low;
- Make sure that you close any open streams and connections.
To keep things brief, just remember that the garbage collector is not an insurance against memory leaks. Tackle GC problems the same way you solve others: find the suspects that cause the problem, identify the actual source of the problem, and then just solve the problem.
If you want to learn more on how to avoid memory leaks, you can check out the following video from the annual Google I/O conference: Memory management for Android Apps at https://www.youtube.com/watch?v=_CruQY55HOk