LeakCanary Memory Leak Detection for Android

repository·main·Indexed 11 days ago

https://github.com/square/leakcanary

A memory leak detection library for Android that helps developers identify and resolve leaks during development. It monitors object lifecycles, triggers heap dumps when objects are retained, and performs on-device analysis using the Shark heap parser. LeakCanary automatically watches standard Android lifecycle components like Activities, Fragments, ViewModels, and Services, providing actionable leak traces from GC roots down to the retained object.

Tokens
47K
Snippets
81
Records
183
Agent score
96%

What's inside LeakCanary

  1. Overview of LeakCanary

    main
    LeakCanary is a memory leak detection library designed specifically for Android applications. It helps developers identify and track down memory leaks by monitoring object lifecycles and detecting when objects that should be garbage collected are still held in memory.
  2. What is Shark?

    main

    Shark is a high-speed, low-memory footprint heap analysis library written in Kotlin. It is the engine that powers LeakCanary 2 and is designed to run in both Java and Android VMs. Shark is modular and released in layers, allowing developers to use specific components depending on their needs:

    • Shark Hprof: For reading and writing records in hprof files.
    • Shark Graph: For navigating the heap object graph.
    • Shark: For generating heap analysis reports.
    • Shark Android: For using Android-specific heuristics to generate tailored reports.
    • Shark CLI: A command-line tool to analyze heaps of debuggable apps on connected Android devices without adding a LeakCanary dependency.
    • Shark Explorer: A desktop application for visualizing heap dumps as navigable treemaps.
    • LeakCanary: The full automated tool built on top of Shark.
  3. Why use LeakCanary to detect memory leaks

    main

    Accumulated memory leaks cause increased memory usage, leading to more frequent Garbage Collection (GC) cycles. This consumes CPU and causes UI jank, freezes, and Application Not Responding (ANR) reports, eventually resulting in OutOfMemoryError (OOME) crashes.

    LeakCanary helps developers identify and fix these leaks during development to prevent production crashes.

    Note on OOME Reporting: Crash reporting tools may not accurately aggregate OutOfMemoryError crashes. Because an OOME can be thrown from any part of the app code when memory is low, each instance may have a unique stacktrace. This causes OOMEs to appear as many distinct, low-occurrence crashes rather than a single high-frequency crash entry.

  4. What is a memory leak?

    main

    In a Java-based runtime, a memory leak is a programming error where an application maintains a reference to an object that is no longer needed. Because a reference still exists, the memory allocated for that object cannot be reclaimed by the Garbage Collector (GC).

    Example: An Android Activity instance is no longer needed after its onDestroy() method is called. If a reference to that instance is stored in a static field, the Activity cannot be garbage collected, resulting in a leak.

  5. Understanding the GC root chain pane

    main

    The chain pane displays the shortest path from a GC root to the selected object. It is organized as follows:

    • Structure: It uses a LazyColumn to display rows, where each row represents one object in the chain. This allows the UI to handle chains with hundreds or thousands of steps efficiently.
    • Layout: The pane is positioned on the far side of the view from the details panel (Chain $\rightarrow$ View $\rightarrow$ Details) to provide a logical flow: where the object came from, where it is, and what it is keeping alive.
    • Navigation: Every object in the chain is clickable. Clicking an object in the chain acts as a way to navigate "out" or "up" to that specific object, similar to clicking its rectangle in the treemap.
    • Chain Views:
      • PathDetail.BRIEF: A condensed view used for the pointer's hover card. It drops the package, address, and instance type, keeping only the retained size inline with the class name to save space.
      • PathDetail.FULL: The complete view shown in the details pane for a clicked object, preserving all technical details of each step.
  6. Extending LeakCanary with custom lifecycle knowledge

    main

    LeakCanary uses heuristics to determine if an object is alive or dead (e.g., knowing that an Application is a singleton and never leaks). You can extend this behavior by providing your own knowledge about your custom types' lifecycles.

    By teaching LeakCanary about your specific types, you allow it to automatically narrow the 'window of unknowns' in leak traces involving your code. This is implemented via an inspector that reads an object and reports conclusions that LeakCanary then uses to automate future analyses.

  7. How Bitmap pixel matching works via native pointers

    main

    Shark Explorer joins retrieved images (from JDWP or am dumpheap -b png) to the heap dump using the bitmap's native pointer as the join key.

    Pointer Reuse Safety

    Because native pointers represent addresses in memory, they can be reused once a bitmap is recycled. To prevent showing the wrong image (pixels from a new bitmap being shown for an old one), the explorer implements a safety check: an image is only accepted if its dimensions match the bitmap it is being joined to. The PNG's IHDR header is used to verify the size. Mismatches are tracked in BitmapCounts.mismatchedCount.

  8. How `ViewChildReferenceReader` handles `ViewGroup` children

    main

    The ViewChildReferenceReader provides a ViewGroup with one virtual reference per child, named by index. This allows the OwnerRule to claim ownership of child views through the parent.

    Implementation Details

    • Virtual References: The reader identifies children by reading mChildren bounded by mChildrenCount. These references are marked as virtual, meaning they are additive to the existing heap structure but do not replace the actual underlying array.
    • Collapsing the Tree: Because the parent now has a direct virtual reference to its children, the dominator tree effectively 'collapses' the levels that previously went through the View[] array. This prevents large View[] arrays from appearing as the primary retainers of a window's memory.
    • Safety and Correctness:
      • The array is still reached via mChildren, ensuring every object remains a node exactly once.
      • The View[] element itself no longer 'owns' the child; it is treated as a rival. This ensures that if a view is in a slot that the parent no longer counts (e.g., during an addViewInner operation), it falls back to the array holding it rather than being incorrectly attributed to a parent that doesn't hold it.
  9. How cancellation works in Shark Explorer reads

    main

    Shark Explorer uses a cooperative cancellation model to ensure that unwanted UI requests (like a user dragging a window or moving a pointer quickly) do not clog the heap dump thread.

    • Mechanism: HeapDumpSession uses a CancelSignal. This signal is checked during every record read and during long analysis stretches.
    • Triggering Cancellation: Cancellation is triggered by cancelling the coroutine that requested the data (e.g., cancelling a LaunchedEffect in Compose).
    • Behavior:
      • If a read is still in the queue when cancelled, the dispatcher drops it before it even starts.
      • If a read is currently in flight, it will finish its current computation stretch before stopping (cancellation lands where the read reads).
    • Safety: All reads are designed to be safe to abandon halfway. Indexes built on first use are by lazy, and internal walks reuse arrays by using generation stamping, ensuring that a cancelled walk leaves no corrupted state.
  10. How LeakCanary detects leaks

    main

    LeakCanary does not monitor aggregate memory measurements or OutOfMemoryError symptoms. Instead, it monitors individual objects.

    When an object's lifecycle ends (e.g., an Activity is destroyed), the app signals that the object should be deleted. LeakCanary watches these specific objects. If the object remains reachable via a chain of references after its lifecycle has ended, it is considered retained, and LeakCanary triggers a heap dump analysis to find the cause.

  11. Understanding semantics and nodes in Shark Explorer UI tests

    main

    When writing UI tests for Shark Explorer, be aware of how Compose semantics work:

    • Clickable Identity Blocks: A clickable identity block is treated as a single semantics node because Modifier.clickable merges its descendants. For example, onNodeWithText("com.example.Holder") will find the entire chain of an identity block if any of its lines match the text. However, the same text appearing in a separate bar above the map is considered a distinct node.
    • Cell Labels: Labels within a cell are painted text and are not visible to the Compose semantics tree. Therefore, tests cannot 'see' or find specific cell labels using text-based node lookups.
    • Waiting for UI Updates: Because cell labels are not nodes, you cannot wait for a specific label to appear. Instead, use waitForTheTree (which waits for the view to stop spinning) or wait for a specific log line that a layout writes to confirm the map has moved.
  12. Understand LeakCanary heap dump thresholds

    main

    LeakCanary does not dump the heap immediately upon finding a single leak. Instead, it waits until a threshold of retained objects is met to avoid excessive performance impact.

    Default Thresholds:

    • App is visible: 5 retained objects.
    • App is not visible (background): 1 retained object.

    If you see a notification about retained objects and then move the app to the background, LeakCanary will dump the heap within 5 seconds. You can also tap the notification to force an immediate heap dump.