Triage an unfamiliar dump¶
The problem¶
Someone hands you a heap dump from an app you did not write, with a note that says "it ran out of memory". You do not know the classes, you cannot ask the author, and you want a first answer in minutes.
The solution¶
Work through these steps in order. Stop as soon as you know enough to ask a good question.
-
Check what you have. Run the summary and read the first line and the tables.
- Does the first line name a format and a size you expect? An empty or cut-off file fails here.
- Do class names look like
a3.t? Then the build is obfuscated. Find the matching mapping file first and add--mappingto every command below. See Deobfuscate release builds. - Is
AllocationSites:present? If so,--allocation-sitescan show where objects were allocated.
-
Ask for suspects. This is the step that needs no prior knowledge.
Here is that step on the small JVM demo from the repository, which has one planted leak:
Heap: 16.97MiB total, 16.68MiB retained-reachable. Threshold: 5.0 % retained share. Showing 1 suspect(s) (1 above threshold). Suspect 1 — 15.79MiB (94.7 % of heap) dominator: java.util.HashMap (object_id=34095644696) accumulating: 4000 instances of byte[], total 15.65MiB path to GC root: Path from object_id=34095644696 (depth 0 step(s)): start ── id=34095644696 → orphan: no holder found in dumpHashMapkeeps 94.7 % of the reachable heap alive, and almost all of it isbyte[]. -
Follow the dominator. Take the dominator's id, and ask who holds it. If the suspect's path ended in
orphan, ask referrers, which also sees static fields:Found 1 target instance(s) for id:34095644696 === Direct referrers (1-hop) === holder.field (or class[] for arrays) ref count shop.SessionCache.BY_ID 1shop.SessionCache.BY_IDis a static field. That is the owner. Find the static that holds a cache goes through this case in full. -
Check what is inside. If the dominator or a top class is a primitive array, the preview already told you what it holds. If it is a collection, ask what class its items are: the suspect's
accumulating:line says so. -
Decide whether it is growing. One dump shows what is alive, not what is growing. Compare it with another dump from the same app, or take a series. See Compare snapshots.
-
Write it down. Save the evidence where others can read it:
How it works¶
The summary gives the shape and the ids. Suspects turn a heap of millions of objects into a short list of owners, ranked by what they keep alive. The tracing modes then explain why each owner exists. Each step uses the output of the one before, so you never need to guess a class name.
Variations¶
| If you find | Do this instead |
|---|---|
| Many leaked instances of one class (activities, view models) | --merge-paths on one of them, to see the holder they share. |
| A giant array and no suspect | Identify a giant array. |
| A package of your own code with many classes in the summary | --target-glob 'com.example.**' --group-holders. |
| An OOM from a JVM | Start with jmap -dump:live or -XX:+HeapDumpOnOutOfMemoryError, then step 2. See JVM dumps. |
| A dump that is too big for the memory you have | Use the summary and --find-referrers only. Retained size and suspects need the whole graph in memory. See Performance. |
Related¶
- Your first triage walks through the same steps on an Android dump.
- Leak suspects explains every part of the report.
- Choose the right mode for the other questions.