xref: /aosp_15_r20/external/leakcanary2/docs/fundamentals-how-leakcanary-works.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1*d9e8da70SAndroid Build Coastguard WorkerOnce LeakCanary is installed, it automatically detects and report memory leaks, in 4 steps:
2*d9e8da70SAndroid Build Coastguard Worker
3*d9e8da70SAndroid Build Coastguard Worker1. Detecting retained objects.
4*d9e8da70SAndroid Build Coastguard Worker2. Dumping the heap.
5*d9e8da70SAndroid Build Coastguard Worker3. Analyzing the heap.
6*d9e8da70SAndroid Build Coastguard Worker4. Categorizing leaks.
7*d9e8da70SAndroid Build Coastguard Worker
8*d9e8da70SAndroid Build Coastguard Worker## 1. Detecting retained objects
9*d9e8da70SAndroid Build Coastguard Worker
10*d9e8da70SAndroid Build Coastguard WorkerLeakCanary 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*d9e8da70SAndroid Build Coastguard Worker
12*d9e8da70SAndroid Build Coastguard Worker* destroyed `Activity` instances
13*d9e8da70SAndroid Build Coastguard Worker* destroyed `Fragment` instances
14*d9e8da70SAndroid Build Coastguard Worker* destroyed fragment `View` instances
15*d9e8da70SAndroid Build Coastguard Worker* cleared `ViewModel` instances
16*d9e8da70SAndroid Build Coastguard Worker
17*d9e8da70SAndroid Build Coastguard WorkerYou can watch any objects that is no longer needed, for example a detached view or a destroyed presenter:
18*d9e8da70SAndroid Build Coastguard Worker
19*d9e8da70SAndroid Build Coastguard Worker```kotlin
20*d9e8da70SAndroid Build Coastguard WorkerAppWatcher.objectWatcher.watch(myDetachedView, "View was detached")
21*d9e8da70SAndroid Build Coastguard Worker```
22*d9e8da70SAndroid Build Coastguard Worker
23*d9e8da70SAndroid Build Coastguard WorkerIf 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*d9e8da70SAndroid Build Coastguard Worker
25*d9e8da70SAndroid Build Coastguard Worker```
26*d9e8da70SAndroid Build Coastguard WorkerD LeakCanary: Watching instance of com.example.leakcanary.MainActivity
27*d9e8da70SAndroid Build Coastguard Worker  (Activity received Activity#onDestroy() callback)
28*d9e8da70SAndroid Build Coastguard Worker
29*d9e8da70SAndroid Build Coastguard Worker... 5 seconds later ...
30*d9e8da70SAndroid Build Coastguard Worker
31*d9e8da70SAndroid Build Coastguard WorkerD LeakCanary: Scheduling check for retained objects because found new object
32*d9e8da70SAndroid Build Coastguard Worker  retained
33*d9e8da70SAndroid Build Coastguard Worker```
34*d9e8da70SAndroid Build Coastguard Worker
35*d9e8da70SAndroid Build Coastguard WorkerLeakCanary waits for the count of retained objects to reach a threshold before dumping the heap, and displays a notification with the latest count.
36*d9e8da70SAndroid Build Coastguard Worker
37*d9e8da70SAndroid Build Coastguard Worker![notification](images/retained-notification.png)
38*d9e8da70SAndroid Build Coastguard Worker**Figure 1.** LeakCanary found 4 retained objects.
39*d9e8da70SAndroid Build Coastguard Worker
40*d9e8da70SAndroid Build Coastguard Worker```
41*d9e8da70SAndroid Build Coastguard WorkerD LeakCanary: Rescheduling check for retained objects in 2000ms because found
42*d9e8da70SAndroid Build Coastguard Worker  only 4 retained objects (< 5 while app visible)
43*d9e8da70SAndroid Build Coastguard Worker```
44*d9e8da70SAndroid Build Coastguard Worker
45*d9e8da70SAndroid Build Coastguard Worker!!! info
46*d9e8da70SAndroid Build Coastguard Worker    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*d9e8da70SAndroid Build Coastguard Worker
48*d9e8da70SAndroid Build Coastguard Worker## 2. Dumping the heap
49*d9e8da70SAndroid Build Coastguard Worker
50*d9e8da70SAndroid Build Coastguard WorkerWhen 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*d9e8da70SAndroid Build Coastguard Worker
52*d9e8da70SAndroid Build Coastguard Worker![toast](images/dumping-toast.png)
53*d9e8da70SAndroid Build Coastguard Worker**Figure 2.** LeakCanary shows a [toast](https://developer.android.com/guide/topics/ui/notifiers/toasts) while dumping the heap.
54*d9e8da70SAndroid Build Coastguard Worker
55*d9e8da70SAndroid Build Coastguard Worker## 3. Analyzing the heap
56*d9e8da70SAndroid Build Coastguard Worker
57*d9e8da70SAndroid Build Coastguard WorkerLeakCanary parses the `.hprof` file using [Shark](shark.md) and locates the retained objects in that heap dump.
58*d9e8da70SAndroid Build Coastguard Worker
59*d9e8da70SAndroid Build Coastguard Worker![done](images/finding-retained-notification.png)
60*d9e8da70SAndroid Build Coastguard Worker**Figure 3.** LeakCanary finds retained objects in the heap dump.
61*d9e8da70SAndroid Build Coastguard Worker
62*d9e8da70SAndroid Build Coastguard WorkerFor 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*d9e8da70SAndroid Build Coastguard Worker
64*d9e8da70SAndroid Build Coastguard Worker![done](images/building-leak-traces-notification.png)
65*d9e8da70SAndroid Build Coastguard Worker**Figure 4.** LeakCanary computes the leak trace for each retained object.
66*d9e8da70SAndroid Build Coastguard Worker
67*d9e8da70SAndroid Build Coastguard WorkerWhen 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*d9e8da70SAndroid Build Coastguard Worker
69*d9e8da70SAndroid Build Coastguard Worker![done](images/analysis-done.png)
70*d9e8da70SAndroid Build Coastguard Worker**Figure 5.** The 4 leak traces turned into 2 distinct leak signatures.
71*d9e8da70SAndroid Build Coastguard Worker
72*d9e8da70SAndroid Build Coastguard Worker
73*d9e8da70SAndroid Build Coastguard Worker```
74*d9e8da70SAndroid Build Coastguard Worker====================================
75*d9e8da70SAndroid Build Coastguard WorkerHEAP ANALYSIS RESULT
76*d9e8da70SAndroid Build Coastguard Worker====================================
77*d9e8da70SAndroid Build Coastguard Worker2 APPLICATION LEAKS
78*d9e8da70SAndroid Build Coastguard Worker
79*d9e8da70SAndroid Build Coastguard WorkerDisplaying only 1 leak trace out of 2 with the same signature
80*d9e8da70SAndroid Build Coastguard WorkerSignature: ce9dee3a1feb859fd3b3a9ff51e3ddfd8efbc6
81*d9e8da70SAndroid Build Coastguard Worker┬───
82*d9e8da70SAndroid Build Coastguard Worker│ GC Root: Local variable in native code
83*d9e8da70SAndroid Build Coastguard Worker84*d9e8da70SAndroid Build Coastguard Worker...
85*d9e8da70SAndroid Build Coastguard Worker```
86*d9e8da70SAndroid Build Coastguard Worker
87*d9e8da70SAndroid Build Coastguard WorkerTapping the notification starts an activity that provides more details. Come back to it again later by tapping the LeakCanary launcher icon:
88*d9e8da70SAndroid Build Coastguard Worker
89*d9e8da70SAndroid Build Coastguard Worker![toast](images/launcher.png)
90*d9e8da70SAndroid Build Coastguard Worker**Figure 6.** LeakCanary adds a launcher icon for each app it's installed in.
91*d9e8da70SAndroid Build Coastguard Worker
92*d9e8da70SAndroid Build Coastguard WorkerEach 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*d9e8da70SAndroid Build Coastguard Worker
94*d9e8da70SAndroid Build Coastguard Worker![toast](images/heap-dump.png)
95*d9e8da70SAndroid Build Coastguard Worker**Figure 7.** The 4 leaks grouped into 2 rows, one for each distinct leak signature.
96*d9e8da70SAndroid Build Coastguard Worker
97*d9e8da70SAndroid Build Coastguard WorkerTap 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*d9e8da70SAndroid Build Coastguard Worker
99*d9e8da70SAndroid Build Coastguard Worker![toast](images/leak-screen.png)
100*d9e8da70SAndroid Build Coastguard Worker**Figure 8.** A screen showing 3 leaks grouped by their common leak signature.
101*d9e8da70SAndroid Build Coastguard Worker
102*d9e8da70SAndroid Build Coastguard WorkerThe **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*d9e8da70SAndroid Build Coastguard Worker
104*d9e8da70SAndroid Build Coastguard Worker![toast](images/signature.png)
105*d9e8da70SAndroid Build Coastguard Worker**Figure 9.** A leak trace with 3 suspect references.
106*d9e8da70SAndroid Build Coastguard Worker
107*d9e8da70SAndroid Build Coastguard WorkerThese same suspicious references are underlined with `~~~` when the leak trace is shared as text:
108*d9e8da70SAndroid Build Coastguard Worker
109*d9e8da70SAndroid Build Coastguard Worker```
110*d9e8da70SAndroid Build Coastguard Worker...
111*d9e8da70SAndroid Build Coastguard Worker112*d9e8da70SAndroid Build Coastguard Worker├─ com.example.leakcanary.LeakingSingleton class
113*d9e8da70SAndroid Build Coastguard Worker│    Leaking: NO (a class is never leaking)
114*d9e8da70SAndroid Build Coastguard Worker│    ↓ static LeakingSingleton.leakedViews
115*d9e8da70SAndroid Build Coastguard Worker│                              ~~~~~~~~~~~
116*d9e8da70SAndroid Build Coastguard Worker├─ java.util.ArrayList instance
117*d9e8da70SAndroid Build Coastguard Worker│    Leaking: UNKNOWN
118*d9e8da70SAndroid Build Coastguard Worker│    ↓ ArrayList.elementData
119*d9e8da70SAndroid Build Coastguard Worker│                ~~~~~~~~~~~
120*d9e8da70SAndroid Build Coastguard Worker├─ java.lang.Object[] array
121*d9e8da70SAndroid Build Coastguard Worker│    Leaking: UNKNOWN
122*d9e8da70SAndroid Build Coastguard Worker│    ↓ Object[].[0]
123*d9e8da70SAndroid Build Coastguard Worker│               ~~~
124*d9e8da70SAndroid Build Coastguard Worker├─ android.widget.TextView instance
125*d9e8da70SAndroid Build Coastguard Worker│    Leaking: YES (View.mContext references a destroyed activity)
126*d9e8da70SAndroid Build Coastguard Worker...
127*d9e8da70SAndroid Build Coastguard Worker```
128*d9e8da70SAndroid Build Coastguard Worker
129*d9e8da70SAndroid Build Coastguard WorkerIn the example above, the signature of the leak would be computed as:
130*d9e8da70SAndroid Build Coastguard Worker
131*d9e8da70SAndroid Build Coastguard Worker```kotlin
132*d9e8da70SAndroid Build Coastguard Workerval leakSignature = sha1Hash(
133*d9e8da70SAndroid Build Coastguard Worker    "com.example.leakcanary.LeakingSingleton.leakedView" +
134*d9e8da70SAndroid Build Coastguard Worker    "java.util.ArrayList.elementData" +
135*d9e8da70SAndroid Build Coastguard Worker    "java.lang.Object[].[x]"
136*d9e8da70SAndroid Build Coastguard Worker)
137*d9e8da70SAndroid Build Coastguard Workerprintln(leakSignature)
138*d9e8da70SAndroid Build Coastguard Worker// dbfa277d7e5624792e8b60bc950cd164190a11aa
139*d9e8da70SAndroid Build Coastguard Worker```
140*d9e8da70SAndroid Build Coastguard Worker
141*d9e8da70SAndroid Build Coastguard Worker## 4. Categorizing leaks
142*d9e8da70SAndroid Build Coastguard Worker
143*d9e8da70SAndroid Build Coastguard WorkerLeakCanary 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*d9e8da70SAndroid Build Coastguard Worker
145*d9e8da70SAndroid Build Coastguard WorkerThe two categories are separated in the result printed in **Logcat**:
146*d9e8da70SAndroid Build Coastguard Worker
147*d9e8da70SAndroid Build Coastguard Worker```
148*d9e8da70SAndroid Build Coastguard Worker====================================
149*d9e8da70SAndroid Build Coastguard WorkerHEAP ANALYSIS RESULT
150*d9e8da70SAndroid Build Coastguard Worker====================================
151*d9e8da70SAndroid Build Coastguard Worker0 APPLICATION LEAKS
152*d9e8da70SAndroid Build Coastguard Worker
153*d9e8da70SAndroid Build Coastguard Worker====================================
154*d9e8da70SAndroid Build Coastguard Worker1 LIBRARY LEAK
155*d9e8da70SAndroid Build Coastguard Worker
156*d9e8da70SAndroid Build Coastguard Worker...
157*d9e8da70SAndroid Build Coastguard Worker┬───
158*d9e8da70SAndroid Build Coastguard Worker│ GC Root: Local variable in native code
159*d9e8da70SAndroid Build Coastguard Worker160*d9e8da70SAndroid Build Coastguard Worker...
161*d9e8da70SAndroid Build Coastguard Worker```
162*d9e8da70SAndroid Build Coastguard Worker
163*d9e8da70SAndroid Build Coastguard WorkerLeakCanary 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*d9e8da70SAndroid Build Coastguard Worker
165*d9e8da70SAndroid Build Coastguard Worker![Library Leak](images/library-leak.png)
166*d9e8da70SAndroid Build Coastguard Worker**Figure 10.** LeakCanary found a Library Leak.
167*d9e8da70SAndroid Build Coastguard Worker
168*d9e8da70SAndroid Build Coastguard WorkerLeakCanary ships with a database of known leaks, which it recognizes by pattern matching on reference names. For example:
169*d9e8da70SAndroid Build Coastguard Worker
170*d9e8da70SAndroid Build Coastguard Worker```
171*d9e8da70SAndroid Build Coastguard WorkerLeak pattern: instance field android.app.Activity$1#this$0
172*d9e8da70SAndroid Build Coastguard WorkerDescription: Android Q added a new IRequestFinishCallback$Stub class [...]
173*d9e8da70SAndroid Build Coastguard Worker┬───
174*d9e8da70SAndroid Build Coastguard Worker│ GC Root: Global variable in native code
175*d9e8da70SAndroid Build Coastguard Worker176*d9e8da70SAndroid Build Coastguard Worker├─ android.app.Activity$1 instance
177*d9e8da70SAndroid Build Coastguard Worker│    Leaking: UNKNOWN
178*d9e8da70SAndroid Build Coastguard Worker│    Anonymous subclass of android.app.IRequestFinishCallback$Stub
179*d9e8da70SAndroid Build Coastguard Worker│    ↓ Activity$1.this$0
180*d9e8da70SAndroid Build Coastguard Worker│                 ~~~~~~
181*d9e8da70SAndroid Build Coastguard Worker╰→ com.example.MainActivity instance
182*d9e8da70SAndroid Build Coastguard Worker```
183*d9e8da70SAndroid Build Coastguard Worker
184*d9e8da70SAndroid Build Coastguard Worker!!! quote "What did I do to cause this leak?"
185*d9e8da70SAndroid Build Coastguard Worker    Nothing wrong! You used an API the way it was intended but the implementation has a bug that is causing this leak.
186*d9e8da70SAndroid Build Coastguard Worker
187*d9e8da70SAndroid Build Coastguard Worker!!! quote "Is there anything I can do to prevent it?"
188*d9e8da70SAndroid Build Coastguard Worker    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*d9e8da70SAndroid Build Coastguard Worker
190*d9e8da70SAndroid Build Coastguard Worker!!! quote "Since I can't do much about this leak, is there a way I can ask LeakCanary to ignore it?"
191*d9e8da70SAndroid Build Coastguard Worker	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*d9e8da70SAndroid Build Coastguard Worker
193*d9e8da70SAndroid Build Coastguard WorkerYou 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*d9e8da70SAndroid Build Coastguard Worker
195*d9e8da70SAndroid Build Coastguard WorkerWhat's next? Learn how to [fix a memory leak](fundamentals-fixing-a-memory-leak.md)!
196