xref: /aosp_15_r20/external/leakcanary2/shark/src/main/java/shark/internal/ShallowSizeCalculator.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1 package shark.internal
2 
3 import shark.HeapGraph
4 import shark.HeapObject.HeapClass
5 import shark.HeapObject.HeapInstance
6 import shark.HeapObject.HeapObjectArray
7 import shark.HeapObject.HeapPrimitiveArray
8 import shark.internal.ObjectArrayReferenceReader.Companion.isSkippablePrimitiveWrapperArray
9 import shark.ValueHolder
10 
11 /**
12  * Provides approximations for the shallow size of objects in memory.
13  *
14  * Determining the actual shallow size of an object in memory is hard, as it changes for each VM
15  * implementation, depending on the various memory layout optimizations and bit alignment.
16  *
17  * More on this topic: https://dev.to/pyricau/the-real-size-of-android-objects-1i2e
18  */
19 internal class ShallowSizeCalculator(private val graph: HeapGraph) {
20 
computeShallowSizenull21   fun computeShallowSize(objectId: Long): Int {
22     return when (val heapObject = graph.findObjectById(objectId)) {
23       is HeapInstance -> {
24         if (heapObject.instanceClassName == "java.lang.String") {
25           // In PathFinder we ignore the value field of String instances when building the dominator
26           // tree, so we add that size back here.
27           val valueObjectId =
28             heapObject["java.lang.String", "value"]?.value?.asNonNullObjectId
29           heapObject.byteSize + if (valueObjectId != null) {
30             computeShallowSize(valueObjectId)
31           } else {
32             0
33           }
34         } else {
35           // Total byte size of fields for instances of this class, as registered in the class dump.
36           // The actual memory layout likely differs.
37           heapObject.byteSize
38         }
39       }
40       // Number of elements * object id size
41       is HeapObjectArray -> {
42         if (heapObject.isSkippablePrimitiveWrapperArray) {
43           // In PathFinder we ignore references from primitive wrapper arrays when building the
44           // dominator tree, so we add that size back here.
45           val elementIds = heapObject.readRecord().elementIds
46           val shallowSize = elementIds.size * graph.identifierByteSize
47           val firstNonNullElement = elementIds.firstOrNull { it != ValueHolder.NULL_REFERENCE }
48           if (firstNonNullElement != null) {
49             val sizeOfOneElement = computeShallowSize(firstNonNullElement)
50             val countOfNonNullElements = elementIds.count { it != ValueHolder.NULL_REFERENCE }
51             shallowSize + (sizeOfOneElement * countOfNonNullElements)
52           } else {
53             shallowSize
54           }
55         } else {
56           heapObject.byteSize
57         }
58       }
59       // Number of elements * primitive type size
60       is HeapPrimitiveArray -> heapObject.byteSize
61       // This is probably way off but is a cheap approximation.
62       is HeapClass -> heapObject.recordSize
63     }
64   }
65 }
66