Retained size¶
The summary ranks classes by shallow size: the bytes each object occupies itself. --retained-size ranks them by
retained size: the bytes that would be freed if the objects were gone, including everything only they keep alive. It answers
the question the summary cannot: is this class small, or does it merely look small because it owns a big subgraph?
Read Shallow and retained size first if the two terms are new.
The retained class table¶
With the flag, the class table gains a Retained column and is sorted by it.
Top 10 classes by retained heap (dominator tree):
+-------------+-----------+-----------+-------------+-----------------------------------------------+
| Shallow | Instances | Retained | Largest | Class name |
+-------------+-----------+-----------+-------------+-----------------------------------------------+
| 2.80MiB | 122154 | 126.84MiB | 24.00bytes | java.util.ArrayList |
| 1.53MiB | 28697 | 58.58MiB | 56.00bytes | com.nexio.tv.domain.model.CatalogRow |
| 56.89MiB | 573552 | 57.37MiB | 104.00bytes | com.nexio.tv.domain.model.MetaPreview |
| 20.79MiB | 908414 | 30.65MiB | 24.00bytes | java.lang.String |
| 21.59MiB | 372795 | 16.37MiB | 5.64MiB | char[] |
| 8.13KiB | 347 | 15.17MiB | 24.00bytes | kotlin.SynchronizedLazyImpl |
| 80.00bytes | 2 | 14.94MiB | 40.00bytes | com.nexio.tv.core.anime.AnimeIdMapAsset |
| 16.00bytes | 1 | 14.94MiB | 16.00bytes | com.nexio.tv.core.anime.AnimeIdMappingService |
| 480.00bytes | 10 | 14.94MiB | 48.00bytes | com.squareup.moshi.LinkedHashTreeMap |
| 3.68MiB | 80397 | 14.21MiB | 48.00bytes | com.squareup.moshi.LinkedHashTreeMap$Node |
+-------------+-----------+-----------+-------------+-----------------------------------------------+
In this Android dump, java.util.ArrayList is 2.80 MiB shallow and 126.84 MiB retained. The lists are small. What they hold is not.
com.nexio.tv.domain.model.CatalogRow is 1.53 MiB shallow and 58.58 MiB retained. And 347 kotlin.SynchronizedLazyImpl wrappers, 8 KiB in total,
keep 15 MiB alive. Sorted by shallow size, none of the three would make the first page.
The rows overlap
Retained sizes are summed per class over every instance, and instances often contain each other. Do not add the column up, and do not compare one row with the total heap. Use it to rank. Why the column overlaps.
The largest retained objects¶
The report ends with the single biggest objects by retained size, with their ids:
Largest retained instances object ids:
27.98MiB object_id=361762400 java.util.ArrayList
27.98MiB object_id=320977512 java.lang.Object[]
27.98MiB object_id=361760560 java.util.ArrayList
27.98MiB object_id=320797680 java.lang.Object[]
14.94MiB object_id=329824280 com.nexio.tv.core.anime.AnimeIdMappingService
14.94MiB object_id=338604528 kotlin.SynchronizedLazyImpl
14.94MiB object_id=338604792 com.nexio.tv.core.anime.AnimeIdMapAsset
7.41MiB object_id=338605000 com.nexio.tv.core.anime.AnimeIdMapIndexes
6.79MiB object_id=369633368 android.util.ArrayMap
6.79MiB object_id=529946720 java.lang.Object[]
These ids feed the tracing modes. object_id=361762400 is the ArrayList that leak suspects names as the first suspect, and
the Object[] on the second line is the array inside it. Objects nest, so the biggest few often tell one story.
On other modes¶
--retained-size also works on two tracing modes:
--paths-from-idprints the retained size of each hop. It tells you how much freeing one reference would reclaim.--find-referrersadds aclass retainedcolumn to each holder row.
heaptrail -i heap.hprof --paths-from-id 1661812752 --max-depth 12 --retained-size --preview-bytes 200
Path from object_id=1661812752 (depth 9 step(s)):
start ── id=1661812752 (retained=5.64MiB)
content: JSON
{"schemaVersion":4,"languageEpoch":0,"languageTag":"en","posterProviderToken":"RPDB:-958971387","cat...
hop 1 ── id=1661812736 (retained=5.64MiB) (via java.lang.String.value)
hop 2 ── id=364312776 (retained=5.64MiB) (via java.util.HashMap$Node.value)
hop 3 ── id=364312696 (retained=5.64MiB) (via java.util.HashMap$Node[][10])
hop 4 ── id=364312512 (retained=5.64MiB) (via java.util.HashMap.table)
hop 5 ── id=364312344 (retained=5.64MiB) (via android.app.SharedPreferencesImpl.mMap)
hop 6 ── id=529946832 (retained=6.79MiB) (via java.lang.Object[][13])
hop 7 ── id=529946800 (retained=6.79MiB) (via android.util.ArrayMap.mArray)
hop 8 ── id=529946720 (retained=6.79MiB) (via java.lang.Object[][1])
hop 9 ── id=369633368 (retained=6.79MiB) (via android.util.ArrayMap.mArray)
Found 122154 target instance(s) for java.util.ArrayList
=== Direct referrers (1-hop) ===
holder.field (or class[] for arrays) ref count class retained
com.nexio.tv.domain.model.MetaPreview.genres 573412 57.37MiB
com.nexio.tv.domain.model.CatalogRow.items 28697 58.58MiB
com.squareup.moshi.LinkedHashTreeMap$Node.value 13743 14.21MiB
com.nexio.tv.core.anime.AnimeIdMapRecord.evidence 12960 5.76MiB
java.util.Collections$UnmodifiableRandomAccessList.c 7613 3.64MiB
java.util.Collections$UnmodifiableRandomAccessList.list 7613 3.64MiB
--leak-suspects turns retained size on by itself.
Weak references¶
By default heaptrail counts weak, soft and phantom references like any other, which is the strict definition and is not what Eclipse MAT
shows. Add --exclude-soft-weak to match MAT's leak-hunting view. See Reference strength.
What it costs¶
Retained size needs the whole graph in memory. On the 224 MiB Android dump, a run took 1.7 seconds and peaked at about 1.4 GiB. On a 186 MiB dump the peak was 0.9 GiB. Plan for roughly five to seven and a half times the file size. Performance has the numbers and how they were measured.
Gotchas¶
- Class statics count as roots. An object held only by a static field is reachable in this graph, even though
--paths-from-idcannot see static fields. - The JSON summary has no retained column.
--jsonwith--retained-sizewrites the same two lists as without it. For retained data in a file, use--leak-suspects --json. - Shallow sizes can differ from the summary's. The retained-size graph sizes objects with its own code, so its totals are not the
summary's
raw shallow heap. Compare within one report.
Related¶
- Leak suspects ranks dominators and explains each one.
- Concepts: shallow and retained size defines the terms.
- Paths to a GC root shows the per-hop retained sizes in context.