How LeakCanary 2.0 Detects Android Memory Leaks: Architecture and Hprof Parsing Explained
This article provides a detailed technical analysis of LeakCanary 2.0, covering its Kotlin‑based integration, the new self‑implemented Hprof parser, detection workflow, core classes such as AppWatcher and ObjectWatcher, graph indexing, and the algorithm used to locate the shortest GC‑root leak path.
Overview
LeakCanary is a widely used Android memory‑leak detection library. Version 2.0 introduces a Kotlin implementation and a custom Hprof file parser (named shark), replacing the previous Java‑based approach.
Integration
For the new version, adding LeakCanary is as simple as adding a Gradle dependency:
dependencies {
// Only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
}Older versions required both Gradle configuration and manual initialization via LeakCanary.install(this).
Automatic Initialization
LeakCanary 2.0 uses a ContentProvider ( AppWatcherInstaller) whose onCreate runs before the application’s onCreate, ensuring automatic setup in the app process.
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class MainProcess : AppWatcherInstaller()
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
// ...
}Note: the ContentProvider runs on the main process, so heavy work should be avoided to prevent slowing app startup.
Core Functionality
LeakCanary 2.0 adds the shark module, which parses Hprof files, builds a heap graph, and finds leak reference chains.
Architecture
Detection Workflow
Identify potentially leaked objects.
Trigger a heap dump and generate an Hprof file.
Parse the Hprof file.
Classify the leak.
Detection Implementation
The library watches four object types: destroyed Activity, destroyed Fragment, destroyed View, and cleared ViewModel. It also monitors objects passed to AppWatcher.objectWatcher.watch(...).
AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")Object watching is performed by ObjectWatcher.watch:
@Synchronized fun watch(watchedObject: Any, description: String) {
if (!isEnabled()) return
removeWeaklyReachableObjects()
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d { "Watching $watchedObject ($description) with key $key" }
watchedObjects[key] = reference
checkRetainedExecutor.execute { moveToRetained(key) }
}Heap Dump Trigger
When retained objects exceed a threshold, the library triggers a heap dump using Android’s native Debug.dumpHprofData and starts HeapAnalyzerService on a background thread.
private fun dumpHeap(retainedReferenceCount: Int, retry: Boolean) {
// ...
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}Hprof File Parsing
The entry point is HeapAnalyzerService.analyzeHeap, which opens the Hprof file, optionally reads a Proguard mapping, and delegates to HeapAnalyzer.analyze to build a HeapGraph.
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
val heapAnalyzer = HeapAnalyzer(this)
val proguardMappingReader = try { ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME)) } catch (e: IOException) { null }
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
objectInspectors = config.objectInspectors,
metadataExtractor = config.metadataExtractor,
proguardMapping = proguardMappingReader?.readProguardMapping()
)
}The Hprof binary protocol defines tags such as STRING, LOAD CLASS, CLASS DUMP, INSTANCE DUMP, OBJECT ARRAY DUMP, PRIMITIVE ARRAY DUMP, and various GC‑root types. The parser extracts these records to populate the graph.
Graph Indexing
To enable fast look‑ups, LeakCanary builds several in‑memory indexes (string cache, class name map, instance index, class index, object‑array index, primitive‑array index).
Finding the Leaking Object
After building the graph, the library searches for the shortest reference chain from any GC‑root to the leaked object using a breadth‑first search implemented in PathFinder:
private fun State.findPathsFromGcRoots(): PathFindingResults {
enqueueGcRoots()
while (queuesNotEmpty) {
val node = poll()
if (checkSeen(node)) throw IllegalStateException("Node $node already visited")
if (node.objectId in leakingObjectIds) {
shortestPathsToLeakingObjects.add(node)
if (shortestPathsToLeakingObjects.size == leakingObjectIds.size) {
if (computeRetainedHeapSize) listener.onAnalysisProgress(FINDING_DOMINATORS) else break
}
}
when (val heapObject = graph.findObjectById(node.objectId)) {
is HeapClass -> visitClassRecord(heapObject, node)
is HeapInstance -> visitInstance(heapObject, node)
is HeapObjectArray -> visitObjectArray(heapObject, node)
}
}
return PathFindingResults(shortestPathsToLeakingObjects, dominatedObjectIds)
}The algorithm enqueues all GC‑roots, then iteratively explores each object, recording paths when a leaking object is encountered, and stops once all leaks are found.
Conclusion
LeakCanary 2.0’s major changes are the migration to Kotlin and the open‑source implementation of its own Hprof parser. The tool converts the binary Hprof dump into a graph, indexes key structures for fast access, and uses a breadth‑first search to locate the shortest GC‑root‑to‑leak path. The detection logic itself remains consistent with earlier versions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
vivo Internet Technology
Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
