<lambda>null1 package shark.internal
2
3 import shark.GcRoot.ThreadObject
4 import shark.HeapGraph
5 import shark.HeapObject.HeapClass
6 import shark.HeapObject.HeapInstance
7 import shark.HeapObject.HeapObjectArray
8 import shark.HeapObject.HeapPrimitiveArray
9 import shark.IgnoredReferenceMatcher
10 import shark.OnAnalysisProgressListener
11 import shark.ValueHolder
12
13 /**
14 * Exposes high level APIs to compute and render a dominator tree. This class
15 * needs to be public to be used by other LeakCanary modules but is internal and
16 * its API might change at any moment.
17 *
18 * Note that the exposed APIs are not optimized for speed, memory or IO.
19 *
20 * Eventually this capability should become part of the Shark public APIs, please
21 * open an issue if you'd like to use this directly.
22 */
23 class ObjectDominators {
24
25 internal data class DominatorNode(
26 val shallowSize: Int,
27 val retainedSize: Int,
28 val retainedCount: Int,
29 val dominatedObjectIds: List<Long>
30 )
31
32 fun renderDominatorTree(
33 graph: HeapGraph,
34 ignoredRefs: List<IgnoredReferenceMatcher>,
35 minRetainedSize: Int,
36 threadName: String? = null,
37 printStringContent: Boolean = false
38 ): String {
39 val stringBuilder = StringBuilder()
40
41 val dominatorTree = buildDominatorTree(graph, ignoredRefs)
42
43 val root = dominatorTree.getValue(ValueHolder.NULL_REFERENCE)
44 stringBuilder.append(
45 "Total retained: ${root.retainedSize} bytes in ${root.retainedCount} objects. Root dominators: ${root.dominatedObjectIds.size}\n\n"
46 )
47
48 val rootIds = if (threadName != null) {
49 setOf(graph.gcRoots.first { gcRoot ->
50 gcRoot is ThreadObject &&
51 graph.objectExists(gcRoot.id) &&
52 graph.findObjectById(gcRoot.id)
53 .asInstance!!["java.lang.Thread", "name"]!!
54 .value.readAsJavaString() == threadName
55 }.id)
56 } else {
57 root.dominatedObjectIds.filter { dominatorTree.getValue(it).retainedSize > minRetainedSize }
58 }
59
60 rootIds
61 .forEach { objectId ->
62 printTree(
63 stringBuilder, graph, dominatorTree, objectId, minRetainedSize, 0, "", true,
64 printStringContent
65 )
66 stringBuilder.append("\n")
67 }
68 return stringBuilder.toString()
69 }
70
71 @Suppress("LongParameterList")
72 private fun printTree(
73 stringBuilder: StringBuilder,
74 graph: HeapGraph,
75 tree: Map<Long, DominatorNode>,
76 objectId: Long,
77 minSize: Int,
78 depth: Int,
79 prefix: String,
80 isLast: Boolean,
81 printStringContent: Boolean
82 ) {
83 val node = tree.getValue(objectId)
84 val heapObject = graph.findObjectById(objectId)
85 val className = when (heapObject) {
86 is HeapClass -> "class ${heapObject.name}"
87 is HeapInstance -> heapObject.instanceClassName
88 is HeapObjectArray -> heapObject.arrayClassName
89 is HeapPrimitiveArray -> heapObject.arrayClassName
90 }
91 val anchor = if (depth == 0) "" else if (isLast) "╰─" else "├─"
92 val size = if (node.retainedSize != node.shallowSize) {
93 "${node.retainedSize} bytes (${node.shallowSize} self)"
94 } else {
95 "${node.shallowSize} bytes"
96 }
97 val count = if (node.retainedCount > 1) {
98 " ${node.retainedCount} objects"
99 } else {
100 ""
101 }
102 val stringContent = if (
103 printStringContent &&
104 heapObject is HeapInstance &&
105 heapObject.instanceClassName == "java.lang.String"
106 ) " \"${heapObject.readAsJavaString()}\"" else ""
107 stringBuilder.append(
108 "$prefix$anchor$className #${heapObject.objectIndex} Retained: $size$count$stringContent\n"
109 )
110
111 val newPrefix = when {
112 depth == 0 -> ""
113 isLast -> {
114 "$prefix "
115 }
116 else -> {
117 "$prefix│ "
118 }
119 }
120
121 val largeChildren = node.dominatedObjectIds.filter { tree.getValue(it).retainedSize > minSize }
122 val lastIndex = node.dominatedObjectIds.lastIndex
123
124 largeChildren.forEachIndexed { index, objectId ->
125 printTree(
126 stringBuilder,
127 graph, tree, objectId, minSize, depth + 1, newPrefix,
128 index == lastIndex,
129 printStringContent
130 )
131 }
132 if (largeChildren.size < node.dominatedObjectIds.size) {
133 stringBuilder.append("$newPrefix╰┄\n")
134 }
135 }
136
137 private fun buildDominatorTree(
138 graph: HeapGraph,
139 ignoredRefs: List<IgnoredReferenceMatcher>
140 ): Map<Long, DominatorNode> {
141 val referenceReader = DelegatingObjectReferenceReader(
142 classReferenceReader = ClassReferenceReader(graph, emptyList()),
143 instanceReferenceReader = ChainingInstanceReferenceReader(
144 listOf(JavaLocalReferenceReader(graph, emptyList())),
145 FieldInstanceReferenceReader(graph, emptyList())
146 ), objectArrayReferenceReader = ObjectArrayReferenceReader()
147 )
148
149 val pathFinder = PathFinder(
150 graph,
151 OnAnalysisProgressListener.NO_OP, referenceReader, ignoredRefs
152 )
153 val nativeSizeMapper = AndroidNativeSizeMapper(graph)
154 val nativeSizes = nativeSizeMapper.mapNativeSizes()
155 val shallowSizeCalculator = ShallowSizeCalculator(graph)
156
157 val result = pathFinder.findPathsFromGcRoots(setOf(), true)
158 return result.dominatorTree!!.buildFullDominatorTree { objectId ->
159 val nativeSize = nativeSizes[objectId] ?: 0
160 val shallowSize = shallowSizeCalculator.computeShallowSize(objectId)
161 nativeSize + shallowSize
162 }
163 }
164 }
165