Mobile Development 15 min read

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.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
How LeakCanary 2.0 Detects Android Memory Leaks: Architecture and Hprof Parsing Explained

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

LeakCanary architecture diagram
LeakCanary architecture diagram

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Mobile DevelopmentAndroidKotlinmemory leakHeap AnalysisLeakCanaryHprof
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.