Shark: Smart Heap Analysis Reports for Kotlin
Shark is the heap analyzer that powers LeakCanary 2. It's a Kotlin standalone heap analysis library that runs at high speed with a low memory footprint.
Shark is released in layers:
A few more things:
The Shark Command Line Interface (CLI) enables you to analyze heaps directly from your computer. It can dump the heap of an app installed on a connected Android device, analyze it, and even strip a heap dump of any sensitive data (e.g. PII, passwords or encryption keys) which is useful when sharing a heap dump.
Install it via Homebrew:
brew install leakcanary-shark
You can also download it [here](https://github.com/square/leakcanary/releases/download/v{{ leak_canary.release }}/shark-cli-{{ leak_canary.release }}.zip).
You can then look for leaks in apps on any connected device, for example:
$ shark-cli --device emulator-5554 --process com.example.app.debug analyze
!!! info shark-cli
works with all debuggable apps, even if they don't include the leakcanary-android
dependency.
Run shark-cli
to see usage instructions:
$ shark-cli Usage: shark-cli [OPTIONS] COMMAND [ARGS]... ^`. .=""=. ^_ \ \ / _ _ \ \ \ { \ | d b | { \ / `~~~--__ \ /\ / { \___----~~' `~~-_/'-=\/=-'\, \ /// a `~. \ \ / /~~~~-, ,__. , /// __,,,,) \ | \/ \/ `~~~; ,---~~-_`/ \ / \/ / / '. .' '._.' _|`~~`|_ /|\ /|\ Options: -p, --process TEXT Full or partial name of a process, e.g. "example" would match "com.example.app" -d, --device ID device/emulator id -m, --obfuscation-mapping PATH path to obfuscation mapping file --verbose / --no-verbose provide additional details as to what shark-cli is doing -h, --hprof FILE path to a .hprof file --help Show this message and exit Commands: interactive Explore a heap dump. analyze Analyze a heap dump. dump-process Dump the heap and pull the hprof file. strip-hprof Replace all primitive arrays from the provided heap dump with arrays of zeroes and generate a new "-stripped.hprof" file.
// build.gradle dependencies { implementation 'com.squareup.leakcanary:shark-hprof:$sharkVersion' }
import java.io.File import shark.Hprof import shark.HprofRecord.StringRecord import shark.OnHprofRecordListener fun main(args: Array<String>) { val heapDumpFile = File(args[0]) // Prints all class and field names Hprof.open(heapDumpFile).use { hprof -> hprof.reader.readHprofRecords( recordTypes = setOf(StringRecord::class), listener = OnHprofRecordListener { position, record -> println((record as StringRecord).string) }, ) } }
// build.gradle dependencies { implementation 'com.squareup.leakcanary:shark-graph:$sharkVersion' }
import java.io.File import shark.Hprof import shark.HprofHeapGraph fun main(args: Array<String>) { val heapDumpFile = File(args[0]) // Prints all thread names Hprof.open(heapDumpFile).use { hprof -> val heapGraph = HprofHeapGraph.indexHprof(hprof) val threadClass = heapGraph.findClassByName("java.lang.Thread")!! val threadNames: Sequence<String> = threadClass.instances.map { instance -> val nameField = instance["java.lang.Thread", "name"]!! nameField.value.readAsJavaString()!! } threadNames.forEach { println(it) } } }
// build.gradle dependencies { implementation 'com.squareup.leakcanary:shark:$sharkVersion' }
import java.io.File import shark.FilteringLeakingObjectFinder import shark.FilteringLeakingObjectFinder.LeakingObjectFilter import shark.HeapAnalyzer import shark.HeapObject import shark.HeapObject.HeapInstance import shark.Hprof import shark.HprofHeapGraph // Marks any instance of com.example.ThingWithLifecycle with // ThingWithLifecycle.destroyed=true as leaking val leakingObjectFilter = object : LeakingObjectFilter { override fun isLeakingObject(heapObject: HeapObject): Boolean { return if ( heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle" ) { val destroyedField = heapObject["com.example.ThingWithLifecycle", "destroyed"]!! destroyedField.value.asBoolean!! } else false } } val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter)) fun main(args: Array<String>) { val heapDumpFile = File(args[0]) val heapAnalysis = Hprof.open(heapDumpFile).use { hprof -> val heapGraph = HprofHeapGraph.indexHprof(hprof) val heapAnalyzer = HeapAnalyzer({}) heapAnalyzer.analyze( heapDumpFile = heapDumpFile, graph = heapGraph, leakingObjectFinder = leakingObjectFinder, ) } println(heapAnalysis) }
// build.gradle dependencies { implementation 'com.squareup.leakcanary:shark-android:$sharkVersion' }
import java.io.File import shark.AndroidObjectInspectors import shark.AndroidReferenceMatchers import shark.FilteringLeakingObjectFinder import shark.FilteringLeakingObjectFinder.LeakingObjectFilter import shark.HeapAnalyzer import shark.HeapObject import shark.HeapObject.HeapInstance import shark.Hprof import shark.HprofHeapGraph // Marks any instance of com.example.ThingWithLifecycle with // ThingWithLifecycle.destroyed=true as leaking val leakingObjectFilter = object : LeakingObjectFilter { override fun isLeakingObject(heapObject: HeapObject): Boolean { return if ( heapObject is HeapInstance && heapObject instanceOf "com.example.ThingWithLifecycle" ) { val instance = heapObject as HeapInstance val destroyedField = instance["com.example.ThingWithLifecycle", "destroyed"]!! destroyedField.value.asBoolean!! } else false } } val leakingObjectFinder = FilteringLeakingObjectFinder(listOf(leakingObjectFilter)) fun main(args: Array<String>) { val heapDumpFile = File(args[0]) val heapAnalysis = Hprof.open(heapDumpFile).use { hprof -> val heapGraph = HprofHeapGraph.indexHprof(hprof) val heapAnalyzer = HeapAnalyzer({}) heapAnalyzer.analyze( heapDumpFile = heapDumpFile, graph = heapGraph, leakingObjectFinder = leakingObjectFinder, referenceMatchers = AndroidReferenceMatchers.appDefaults, objectInspectors = AndroidObjectInspectors.appDefaults, ) } println(heapAnalysis) }