-
-
Save jush/87ac548fe2c3379938b57cc3e961b72c to your computer and use it in GitHub Desktop.
A standalone Kotlin script that shows how to do custom heap analysis work with Shark, the heap parsing library that powers LeakCanary (see https://square.github.io/leakcanary/shark)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env -S JAVA_OPTS="-Xmx16g" kotlin | |
// In order to analyze big heaps you might need to modify above `-Xmx16g` Java option. | |
// Before running this script, install Kotlin with `brew install kotlin` | |
// Then run this with `kotlin shark-custom-script.main.kts` | |
// Edit this script in the latest Android Studio or IntelliJ IDEA releases to get dependency import and auto completion. | |
@file:Repository("https://repo.maven.apache.org/maven2/") | |
@file:DependsOn("com.squareup.leakcanary:shark-android:2.13") | |
import java.io.File | |
import shark.AndroidMetadataExtractor | |
import shark.AndroidObjectInspectors | |
import shark.AndroidReferenceMatchers | |
import shark.FilteringLeakingObjectFinder | |
import shark.HeapAnalyzer | |
import shark.HeapObject.HeapInstance | |
import shark.HprofHeapGraph.Companion.openHeapGraph | |
val heapDumpFile: File = TODO("Replace this with hprof file") // = File("PATH_TO_HPROF") | |
val heapAnalyzer = HeapAnalyzer(listener = { step -> | |
println("Analysis in progress, working on: ${step.name}") | |
}) | |
heapDumpFile.openHeapGraph( | |
proguardMapping = null | |
).use { graph -> | |
println("Looking for known leaks $heapDumpFile") | |
val heapAnalysis = heapAnalyzer.analyze( | |
heapDumpFile, | |
graph = graph, | |
leakingObjectFinder = FilteringLeakingObjectFinder( | |
AndroidObjectInspectors.appLeakingObjectFilters | |
), | |
referenceMatchers = AndroidReferenceMatchers.appDefaults, | |
computeRetainedHeapSize = true, | |
objectInspectors = AndroidObjectInspectors.appDefaults, | |
metadataExtractor = AndroidMetadataExtractor, | |
) | |
println(heapAnalysis) | |
println("Let's do some custom heap analysis just for fun.") | |
println("#####") | |
println("String instance count: ${graph.findClassByName("java.lang.String")!!.instances.count()}") | |
println("#####") | |
val dexPaths = | |
graph.findClassByName("dalvik.system.BaseDexClassLoader")!!.instances.flatMap { classLoader -> | |
val pathList = classLoader["dalvik.system.BaseDexClassLoader", "pathList"]!! | |
.valueAsInstance!! | |
pathList["dalvik.system.DexPathList", "dexElements"]!! | |
.valueAsObjectArray!!.readElements() | |
.mapNotNull { | |
it.asObject?.asInstance | |
}.map { dexElement -> | |
val pathFile = dexElement["dalvik.system.DexPathList\$Element", "path"]!! | |
.valueAsInstance!! | |
pathFile["java.io.File", "path"]!!.valueAsInstance!!.readAsJavaString() | |
} | |
} | |
println("All dex paths:\n${dexPaths.joinToString("\n")}") | |
println("#####") | |
val generatedComponentClasses = graph.classes.filter { heapClass -> | |
val classSimpleName = heapClass.simpleName | |
classSimpleName.startsWith("Dagger") && | |
classSimpleName.endsWith("Impl") | |
} | |
val componentInstances = generatedComponentClasses.flatMap { it.instances } | |
val allSingletons = componentInstances.flatMap { it.componentSingletons } | |
println( | |
"Found ${componentInstances.count()} Dagger component instances " + | |
"and ${allSingletons.count()} Dagger singleton instances in heap." | |
) | |
} | |
private val HeapInstance.componentSingletons: Sequence<Long> | |
get() = readFields().mapNotNull { componentField -> | |
val componentFieldInstance = componentField.valueAsInstance ?: return@mapNotNull null | |
val provider = if (componentFieldInstance instanceOf "dagger.internal.DelegateFactory") { | |
componentFieldInstance["dagger.internal.DelegateFactory", "delegate"]?.valueAsInstance | |
} else { | |
componentFieldInstance | |
} | |
if (provider == null || !(provider instanceOf "dagger.internal.DoubleCheck")) { | |
return@mapNotNull null | |
} | |
val singletonObjectId = | |
provider["dagger.internal.DoubleCheck", "instance"]!!.value.asNonNullObjectId!! | |
val doubleCheckUninitializedObjectId = graph.context.getOrPut("DoubleCheck.UNINITIALIZED") { | |
checkNotNull(graph.findClassByName("dagger.internal.DoubleCheck")) { | |
"The Dagger DoubleCheck class should always be in the classpath." | |
}["UNINITIALIZED"]!!.valueAsInstance!!.objectId | |
} | |
if (singletonObjectId != doubleCheckUninitializedObjectId) { | |
singletonObjectId | |
} else { | |
null | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment