<lambda>null1 package shark.internal
2
3 import shark.GcRoot
4 import shark.GcRoot.JavaFrame
5 import shark.GcRoot.JniGlobal
6 import shark.GcRoot.ThreadObject
7 import shark.HeapGraph
8 import shark.HeapObject
9 import shark.HeapObject.HeapClass
10 import shark.HeapObject.HeapInstance
11 import shark.HeapObject.HeapObjectArray
12 import shark.HeapObject.HeapPrimitiveArray
13 import shark.IgnoredReferenceMatcher
14 import shark.LibraryLeakReferenceMatcher
15 import shark.ReferenceMatcher
16 import shark.ReferencePattern.NativeGlobalVariablePattern
17 import shark.filterFor
18
19 /**
20 * Extracted from PathFinder, this should eventually be part of public API surface
21 * and we should likely also revisit the gc root type filtering which happens during
22 * heap parsing, as that's not really a concern for the heap parser and more for path
23 * finding. There are probably memory concerns as well there though. We could:
24 * - compress the storing of these roots
25 * - keep only the roots locations and read / deserialize as needed
26 * - Ensure a unique / consistent view of roots by doing the work of GcRootProvider
27 * at parsing time and keeping that list.
28 */
29 internal class GcRootProvider(
30 private val graph: HeapGraph,
31 referenceMatchers: List<ReferenceMatcher>
32 ) {
33
34 private val jniGlobalReferenceMatchers: Map<String, ReferenceMatcher>
35
36 init {
37 val jniGlobals = mutableMapOf<String, ReferenceMatcher>()
38 referenceMatchers.filterFor(graph).forEach { referenceMatcher ->
39 when (val pattern = referenceMatcher.pattern) {
40 is NativeGlobalVariablePattern -> {
41 jniGlobals[pattern.className] = referenceMatcher
42 }
43 }
44 }
45 this.jniGlobalReferenceMatchers = jniGlobals
46 }
47
48 class GcRootReference(
49 val gcRoot: GcRoot,
50 val isLowPriority: Boolean,
51 val matchedLibraryLeak: LibraryLeakReferenceMatcher?,
52 )
53
54 fun provideGcRoots(): Sequence<GcRootReference> {
55 return sortedGcRoots().asSequence().mapNotNull { (heapObject, gcRoot) ->
56 when (gcRoot) {
57 // Note: in sortedGcRoots we already filter out any java frame that has an associated
58 // thread. These are the remaining ones (shouldn't be any, this is just in case).
59 is JavaFrame -> {
60 GcRootReference(
61 gcRoot,
62 isLowPriority = true,
63 matchedLibraryLeak = null
64 )
65 }
66 is JniGlobal -> {
67 val referenceMatcher = when (heapObject) {
68 is HeapClass -> jniGlobalReferenceMatchers[heapObject.name]
69 is HeapInstance -> jniGlobalReferenceMatchers[heapObject.instanceClassName]
70 is HeapObjectArray -> jniGlobalReferenceMatchers[heapObject.arrayClassName]
71 is HeapPrimitiveArray -> jniGlobalReferenceMatchers[heapObject.arrayClassName]
72 }
73 if (referenceMatcher !is IgnoredReferenceMatcher) {
74 if (referenceMatcher is LibraryLeakReferenceMatcher) {
75 GcRootReference(
76 gcRoot,
77 isLowPriority = true,
78 matchedLibraryLeak = referenceMatcher
79 )
80 } else {
81 GcRootReference(
82 gcRoot,
83 isLowPriority = false,
84 matchedLibraryLeak = null
85 )
86 }
87 } else {
88 null
89 }
90 }
91 else -> {
92 GcRootReference(
93 gcRoot,
94 isLowPriority = false,
95 matchedLibraryLeak = null
96 )
97 }
98 }
99 }
100 }
101
102 /**
103 * Sorting GC roots to get stable shortest path
104 * Once sorted all ThreadObject Gc Roots are located before JavaLocalPattern Gc Roots.
105 * This ensures ThreadObjects are visited before JavaFrames, and threadsBySerialNumber can be
106 * built before JavaFrames.
107 */
108 private fun sortedGcRoots(): List<Pair<HeapObject, GcRoot>> {
109 val rootClassName: (HeapObject) -> String = { graphObject ->
110 when (graphObject) {
111 is HeapClass -> {
112 graphObject.name
113 }
114 is HeapInstance -> {
115 graphObject.instanceClassName
116 }
117 is HeapObjectArray -> {
118 graphObject.arrayClassName
119 }
120 is HeapPrimitiveArray -> {
121 graphObject.arrayClassName
122 }
123 }
124 }
125
126 val threadSerialNumbers =
127 ThreadObjects.getThreadObjects(graph).map { it.threadSerialNumber }.toSet()
128
129 return graph.gcRoots
130 .filter { gcRoot ->
131 // GC roots sometimes reference objects that don't exist in the heap dump
132 // See https://github.com/square/leakcanary/issues/1516
133 graph.objectExists(gcRoot.id) &&
134 // Only include java frames that do not have a corresponding ThreadObject.
135 // JavaLocalReferenceReader will insert the other java frames.
136 !(gcRoot is JavaFrame && gcRoot.threadSerialNumber in threadSerialNumbers)
137 }
138 .map { graph.findObjectById(it.id) to it }
139 .sortedWith { (graphObject1, root1), (graphObject2, root2) ->
140 // Sorting based on pattern name first, but we want ThreadObjects to be first because
141 // they'll later enqueue java frames via JavaLocalReferenceReader in the low priority queue
142 // and we want those java frames at the head of the low priority queue.
143 if (root1 is ThreadObject && root2 !is ThreadObject) {
144 return@sortedWith -1
145 } else if (root1 !is ThreadObject && root2 is ThreadObject) {
146 return@sortedWith 1
147 }
148 val gcRootTypeComparison = root2::class.java.name.compareTo(root1::class.java.name)
149 if (gcRootTypeComparison != 0) {
150 gcRootTypeComparison
151 } else {
152 rootClassName(graphObject1).compareTo(rootClassName(graphObject2))
153 }
154 }
155 }
156 }
157