xref: /aosp_15_r20/external/leakcanary2/docs/fundamentals-how-leakcanary-works.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1Once LeakCanary is installed, it automatically detects and report memory leaks, in 4 steps:
2
31. Detecting retained objects.
42. Dumping the heap.
53. Analyzing the heap.
64. Categorizing leaks.
7
8## 1. Detecting retained objects
9
10LeakCanary hooks into the Android lifecycle to automatically detect when activities and fragments are destroyed and should be garbage collected. These destroyed objects are passed to an `ObjectWatcher`, which holds [weak references](https://en.wikipedia.org/wiki/Weak_reference) to them. LeakCanary automatically detects leaks for the following objects:
11
12* destroyed `Activity` instances
13* destroyed `Fragment` instances
14* destroyed fragment `View` instances
15* cleared `ViewModel` instances
16
17You can watch any objects that is no longer needed, for example a detached view or a destroyed presenter:
18
19```kotlin
20AppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
21```
22
23If the weak reference held by `ObjectWatcher` isn't cleared after **waiting 5 seconds** and running garbage collection, the watched object is considered **retained**, and potentially leaking. LeakCanary logs this to Logcat:
24
25```
26D LeakCanary: Watching instance of com.example.leakcanary.MainActivity
27  (Activity received Activity#onDestroy() callback)
28
29... 5 seconds later ...
30
31D LeakCanary: Scheduling check for retained objects because found new object
32  retained
33```
34
35LeakCanary waits for the count of retained objects to reach a threshold before dumping the heap, and displays a notification with the latest count.
36
37![notification](images/retained-notification.png)
38**Figure 1.** LeakCanary found 4 retained objects.
39
40```
41D LeakCanary: Rescheduling check for retained objects in 2000ms because found
42  only 4 retained objects (< 5 while app visible)
43```
44
45!!! info
46    The default threshold is **5 retained objects** when the app is **visible**, and **1 retained object** when the app is **not visible**. If you see the retained objects notification and then put the app in background (for example by pressing the Home button), then the threshold changes from 5 to 1 and LeakCanary dumps the heap within 5 seconds. Tapping the notification forces LeakCanary to dump the heap immediately.
47
48## 2. Dumping the heap
49
50When the count of retained objects reaches a threshold, LeakCanary dumps the Java heap into a `.hprof` file (a **heap dump**) stored onto the Android file system (see [Where does LeakCanary store heap dumps?](faq.md#where-does-leakcanary-store-heap-dumps)). Dumping the heap freezes the app for a short amount of time, during which LeakCanary displays the following toast:
51
52![toast](images/dumping-toast.png)
53**Figure 2.** LeakCanary shows a [toast](https://developer.android.com/guide/topics/ui/notifiers/toasts) while dumping the heap.
54
55## 3. Analyzing the heap
56
57LeakCanary parses the `.hprof` file using [Shark](shark.md) and locates the retained objects in that heap dump.
58
59![done](images/finding-retained-notification.png)
60**Figure 3.** LeakCanary finds retained objects in the heap dump.
61
62For each retained object, LeakCanary finds the path of references that prevents that retained object from being garbage collected: its **leak trace**. You will learn to analyze a leak trace in the next section: [Fixing a memory leak](fundamentals-fixing-a-memory-leak.md).
63
64![done](images/building-leak-traces-notification.png)
65**Figure 4.** LeakCanary computes the leak trace for each retained object.
66
67When the analysis is done, LeakCanary displays a **notification** with a summary, and also prints the result in **Logcat**. Notice below how the **4 retained objects** are grouped as **2 distinct leaks**. LeakCanary creates a **signature for each leak trace**, and groups together leaks that have the same signature, ie leaks that are caused by the same bug.
68
69![done](images/analysis-done.png)
70**Figure 5.** The 4 leak traces turned into 2 distinct leak signatures.
71
72
73```
74====================================
75HEAP ANALYSIS RESULT
76====================================
772 APPLICATION LEAKS
78
79Displaying only 1 leak trace out of 2 with the same signature
80Signature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
81┬───
82│ GC Root: Local variable in native code
8384...
85```
86
87Tapping the notification starts an activity that provides more details. Come back to it again later by tapping the LeakCanary launcher icon:
88
89![toast](images/launcher.png)
90**Figure 6.** LeakCanary adds a launcher icon for each app it's installed in.
91
92Each row corresponds to a **group of leaks with the same signature**. LeakCanary  marks a row as <span style="border-radius: 20px; background: #ffd24c; padding-left: 8px; padding-right: 8px; padding-top: 2px; padding-bottom: 2px; color: #141c1f;">New</span> the first time the app triggers a leak with that signature.
93
94![toast](images/heap-dump.png)
95**Figure 7.** The 4 leaks grouped into 2 rows, one for each distinct leak signature.
96
97Tap on a leak to open up a screen with the leak trace. You can toggle between retained objects and their leak trace via a drop down.
98
99![toast](images/leak-screen.png)
100**Figure 8.** A screen showing 3 leaks grouped by their common leak signature.
101
102The **leak signature** is the **hash of the concatenation of each <span style="color: #9976a8;">reference</span> suspected to cause the leak**, ie each reference **<span style="text-decoration: underline; text-decoration-color: red; text-decoration-style: wavy; color: #9976a8;">displayed with a red underline</span>**:
103
104![toast](images/signature.png)
105**Figure 9.** A leak trace with 3 suspect references.
106
107These same suspicious references are underlined with `~~~` when the leak trace is shared as text:
108
109```
110...
111112├─ com.example.leakcanary.LeakingSingleton class
113│    Leaking: NO (a class is never leaking)
114│    ↓ static LeakingSingleton.leakedViews
115│                              ~~~~~~~~~~~
116├─ java.util.ArrayList instance
117│    Leaking: UNKNOWN
118│    ↓ ArrayList.elementData
119│                ~~~~~~~~~~~
120├─ java.lang.Object[] array
121│    Leaking: UNKNOWN
122│    ↓ Object[].[0]
123│               ~~~
124├─ android.widget.TextView instance
125│    Leaking: YES (View.mContext references a destroyed activity)
126...
127```
128
129In the example above, the signature of the leak would be computed as:
130
131```kotlin
132val leakSignature = sha1Hash(
133    "com.example.leakcanary.LeakingSingleton.leakedView" +
134    "java.util.ArrayList.elementData" +
135    "java.lang.Object[].[x]"
136)
137println(leakSignature)
138// dbfa277d7e5624792e8b60bc950cd164190a11aa
139```
140
141## 4. Categorizing leaks
142
143LeakCanary separates the leaks it finds in your app into two categories: **Application Leaks** and **Library Leaks**. A **Library Leak** is a leak caused by a known bug in 3rd party code that you do not have control over. This leak is impacting your application, but unfortunately fixing it may not be in your control so LeakCanary separates it out.
144
145The two categories are separated in the result printed in **Logcat**:
146
147```
148====================================
149HEAP ANALYSIS RESULT
150====================================
1510 APPLICATION LEAKS
152
153====================================
1541 LIBRARY LEAK
155
156...
157┬───
158│ GC Root: Local variable in native code
159160...
161```
162
163LeakCanary marks a row as a <span style="border-radius: 20px; background: #4e462f; padding-left: 8px; padding-right: 8px; padding-top: 2px; padding-bottom: 2px; color: #ffcc32;">Library Leak</span> in its list of leaks:
164
165![Library Leak](images/library-leak.png)
166**Figure 10.** LeakCanary found a Library Leak.
167
168LeakCanary ships with a database of known leaks, which it recognizes by pattern matching on reference names. For example:
169
170```
171Leak pattern: instance field android.app.Activity$1#this$0
172Description: Android Q added a new IRequestFinishCallback$Stub class [...]
173┬───
174│ GC Root: Global variable in native code
175176├─ android.app.Activity$1 instance
177│    Leaking: UNKNOWN
178│    Anonymous subclass of android.app.IRequestFinishCallback$Stub
179│    ↓ Activity$1.this$0
180│                 ~~~~~~
181╰→ com.example.MainActivity instance
182```
183
184!!! quote "What did I do to cause this leak?"
185    Nothing wrong! You used an API the way it was intended but the implementation has a bug that is causing this leak.
186
187!!! quote "Is there anything I can do to prevent it?"
188    Maybe! Some Library Leaks can be fixed using reflection, others by exercising a code path that makes the leak go away. This type of fix tends to be hacky, so beware! Your best option might be to find the bug report or file one, and insist that the bug gets fixed.
189
190!!! quote "Since I can't do much about this leak, is there a way I can ask LeakCanary to ignore it?"
191	There's no way for LeakCanary to know whether a leak is a Library Leak prior to dumping the heap and analyzing it. If LeakCanary didn't show the result notification when a Library Leak is found then you'd start wondering what happened to the LeakCanary analysis after the dumping toast.
192
193You can see the full list of known leaks in the [AndroidReferenceMatchers](https://github.com/square/leakcanary/blob/main/shark-android/src/main/java/shark/AndroidReferenceMatchers.kt#L49) class. If you find an Android SDK leak that isn't recognized, please [report it](faq.md#can-a-leak-be-caused-by-the-android-sdk). You can also [customize the list of known Library Leaks](recipes.md#matching-known-library-leaks).
194
195What's next? Learn how to [fix a memory leak](fundamentals-fixing-a-memory-leak.md)!
196