This is the first post in a series about debugging Android memory issues—subsequent posts will deal with using Eclipse Memory Analyzer to inspect heap dumps, as well as looking at common Android-specific memory consumption pitfalls. We’ll start with Programmatic Heap Dumping.
The contents of this post ought to be useful to developers new to the Android platform (regardless of Java experience) as it details Android-specific APIs and the use of Android SDK utilities to successfully interoperate with the standard JVM toolchain (e.g.
hprof-conv which converts Dalvik heap dumps into the J2SE HPROF format, expected by Eclipse Memory Analyzer; MAT). Once this has been accomplished, the process of debugging leaks is more or less identical for Android applications and standard Java code running on a traditional JVM.
What’s a Heap?
All we really need to know about the heap for the purposes of this post is that it's a slab of VM-managed memory into which (most) Java objects are allocated—certainly all of the objects we're likely to care about. When we're talking about the heap size of a particular object—how much of the heap it occupies—we talk of Shallow Size (or Shallow Heap) and Retained Size (or Retained Heap). The shallow size is the amount required for an object itself, while the retained size is the amount required for an object and all of the objects it refers to (i.e. via instance attributes).
A heap dump is a portable (i.e. file-based) representation of a VM's heap at a particular point in time.
Obtaining Heap Dumps On Demand
Interactively getting an Android process to dump its heap is trivial with DDMS (there's a
Dump HPROF File button). That's not our concern; we're focused on obtaining dumps at particular points in a program's execution by using the android.os.Debug API, which is an approach we might want to take if pathological memory consumption is peaking at points which aren't easily identified through interactive use.
That said, our example is fairly contrived—an in-app Button which triggers a heap dump is isomorphic to the
Dump HPROF File DDMS feature—figuring out when to trigger the dumping is entirely application-specific—the code samples are to illustrate how to do it, using a triggering mechanism likely to be familiar to anybody who's written an interactive Android application.
Here's an example of a View.OnClickListener which accepts a
String data directory, and writes a heap dump within it, when the
onClick method is invoked:
Below is an excerpt of an Activity which attaches the above listener to a Button, and passes in a sensible value for the path prefix, so that the dump ends up somewhere useful (note that this method will overwrite an existing file at the supplied path):
Dump Heap button is activated, we'll be able to retrieve the file from the Android device/emulator by executing the following command on the host machine (assuming
$ANDROID_HOME/platform-tools is in
% adb pull /data/data/com.novoda.example.MemoryLeaker/MemoryLeaker.dalvik-hprof 226 KB/s (9160365 bytes in 17.973s)
If we plan to do anything with the heap dump, we'll need to convert it to the J2SE format, as outlined above, using the
hprof-conv binary, in
$ANDROID_HOME/tools (which ought to be in
% hprof-conv MemoryLeaker.dalvik-hprof MemoryLeaker.hprof % ls -lh MemoryLeaker.hprof -rw-r--r-- 1 moe staff 9.2M 18 Apr 15:33 MemoryLeaker.hprof
Grabbing a Heap Dump When Things Go Wrong
Alternatively, if we're dealing with a worst-case scenario and have absolutely no idea what's causing the leakage, or lack the patience to babysit the application until it falls apart, we can install a handler which'll catch an OutOfMemoryError and try to write a snapshot of heap use at that point. Alternatives would be using either
Application.onLowMemory, which have vaguely defined semantics, and are not as definitively catastrophic as an uncaught OOM (from Application's documentation: "this is called when the overall system is running low on memory, and would like actively running process to try to tighten their belt. While the exact point at which this will be called is not defined…").
Back to Uncaught exception handlers: these are per-Thread, which is clear from the entrypoint—Thread's
setUncaughtExceptionHandler instance method. Below is an example of a Thread.UncaughtExceptionHandler suitable for passing in:
And the code to set this up at
The next post in this series will detail how to use MAT to find likely culprits in a heap dump which has been fetched and converted using the above method.