Memory: Java heap profiler

NOTE: The Java heap profiler requires Android 11 or higher

See the Memory Guide for getting started with Java heap profiling.

Conversely from the Native heap profiler, the Java heap profiler reports full retention graphs of managed objects but not call-stacks. The information recorded by the Java heap profiler is of the form: Object X retains object Y, which is N bytes large, through its class member named Z.

UI

Heap graph dumps are shown as flamegraphs in the UI after clicking on the diamond in the “Heap Profile” track of a process. Each diamond corresponds to a heap dump.

Java heap profiles in the process tracks

Flamegraph of a Java heap profiler

The native size of certain objects is represented as an extra child node in the flamegraph, prefixed with “[native]”. The extra node counts as an extra object. This is available only on Android T+.

SQL

Information about the Java Heap is written to the following tables:

native_size (available only on Android T+) is extracted from the related libcore.util.NativeAllocationRegistry and is not included in self_size.

For instance, to get the bytes used by class name, run the following query. As-is this query will often return un-actionable information, as most of the bytes in the Java heap end up being primitive arrays or strings.

select c.name, sum(o.self_size)
       from heap_graph_object o join heap_graph_class c on (o.type_id = c.id)
       where reachable = 1 group by 1 order by 2 desc;
namesum(o.self_size)
java.lang.String2770504
long[]1500048
int[]1181164
java.lang.Object[]624812
char[]357720
byte[]350423

We can use experimental_flamegraph to normalize the graph into a tree, always taking the shortest path to the root and get cumulative sizes. Note that this is experimental and the API is subject to change. From this we can see how much memory is being held by each type of object

For that, we need to find the timestamp and upid of the graph.

select distinct graph_sample_ts, upid from heap_graph_object
graph_sample_tsupid
567856468011

We can then use them to get the flamegraph data.

select name, cumulative_size
       from experimental_flamegraph
       where ts = 56785646801
            and upid = 1
            and profile_type = 'graph'
       order by 2 desc;
namecumulative_size
java.lang.String1431688
java.lang.Class<android.icu.text.Transliterator>1120227
android.icu.text.TransliteratorRegistry1119600
com.android.systemui.statusbar.phone.StatusBarNotificationPresenter$21086209
com.android.systemui.statusbar.phone.StatusBarNotificationPresenter1085593
java.util.Collections$SynchronizedMap1063376
java.util.HashMap1063292

TraceConfig

The Java heap profiler is configured through the JavaHprofConfig section of the trace config.

data_sources {
  config {
    name: "android.java_hprof"
    java_hprof_config {
      process_cmdline: "com.google.android.inputmethod.latin"
      dump_smaps: true
    }
  }
}