xref: /aosp_15_r20/external/leakcanary2/docs/fundamentals-fixing-a-memory-leak.md (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)
1A memory leak is a programming error that causes an application to keep a reference to an object that is no longer needed. Somewhere in the code, there's a reference that should have been cleared and wasn't.
2
3Follow these 4 steps to fix memory leaks:
4
51. Find the leak trace.
62. Narrow down the suspect references.
73. Find the reference causing the leak.
84. Fix the leak.
9
10LeakCanary helps you with the first two steps. The last two steps are up to you!
11
12## 1. Find the leak trace
13
14A **leak trace** is a shorter name for the *best strong reference path from garbage collection roots to the retained object*, ie the path of references that is holding an object in memory, therefore preventing it from being garbage collected.
15
16For example, let's store a helper singleton in a static field:
17
18```java
19class Helper {
20}
21
22class Utils {
23  public static Helper helper = new Helper();
24}
25```
26
27Let's tell LeakCanary that the singleton instance is expected to be garbage collected:
28
29```
30AppWatcher.objectWatcher.watch(Utils.helper)
31```
32
33The leak trace for that singleton looks like this:
34
35```
36┬───
37│ GC Root: Local variable in native code
3839├─ dalvik.system.PathClassLoader instance
40│    ↓ PathClassLoader.runtimeInternalObjects
41├─ java.lang.Object[] array
42│    ↓ Object[].[43]
43├─ com.example.Utils class
44│    ↓ static Utils.helper
45╰→ java.example.Helper
46```
47
48Let's break it down! At the top, a `PathClassLoader` instance is held by a **garbage collection (GC) root**, more specifically a local variable in native code. GC roots are special objects that are always reachable, ie they cannot be garbage collected. There are 4 main types of GC root:
49
50* **Local variables**, which belong to the stack of a thread.
51* Instances of **active Java threads**.
52* **System Classes**, which never unload.
53* **Native references**, which are controlled by native code.
54
55```
56┬───
57│ GC Root: Local variable in native code
5859├─ dalvik.system.PathClassLoader instance
60```
61
62A line starting with `├─ ` represents a Java object (either a class, an object array or an instance), and a line starting with `│    ↓ ` represents a reference to the Java object on the next line.
63
64`PathClassLoader` has a `runtimeInternalObjects` field that is a reference to an array of `Object`:
65
66```
67├─ dalvik.system.PathClassLoader instance
68│    ↓ PathClassLoader.runtimeInternalObjects
69├─ java.lang.Object[] array
70```
71
72The element at position 43 in that array of `Object` is a reference to the `Utils` class.
73
74```
75├─ java.lang.Object[] array
76│    ↓ Object[].[43]
77├─ com.example.Utils class
78```
79
80A line starting with `╰→ ` represents the leaking object, ie the object that is passed to [AppWatcher.objectWatcher.watch()](/leakcanary/api/leakcanary-object-watcher-android/leakcanary/-app-watcher/object-watcher/).
81
82The `Utils` class has a static `helper` field which is a reference to the leaking object, which is the Helper singleton instance:
83
84```
85├─ com.example.Utils class
86│    ↓ static Utils.helper
87╰→ java.example.Helper instance
88```
89
90## 2. Narrow down the suspect references
91
92A leak trace is a path of references. Initially, all references in that path are suspected of causing the leak, but LeakCanary can automatically narrow down the suspect references. To understand what that means, let's go through that process manually.
93
94Here's an example of bad Android code:
95
96```kotlin
97class ExampleApplication : Application() {
98  val leakedViews = mutableListOf<View>()
99}
100
101class MainActivity : Activity() {
102  override fun onCreate(savedInstanceState: Bundle?) {
103    super.onCreate(savedInstanceState)
104    setContentView(R.layout.main_activity)
105
106	val textView = findViewById<View>(R.id.helper_text)
107
108    val app = application as ExampleApplication
109	// This creates a leak, What a Terrible Failure!
110	app.leakedViews.add(textView)
111  }
112}
113```
114
115LeakCanary produces a leak trace that looks like this:
116
117```
118┬───
119│ GC Root: System class
120121├─ android.provider.FontsContract class
122│    ↓ static FontsContract.sContext
123├─ com.example.leakcanary.ExampleApplication instance
124│    ↓ ExampleApplication.leakedViews
125├─ java.util.ArrayList instance
126│    ↓ ArrayList.elementData
127├─ java.lang.Object[] array
128│    ↓ Object[].[0]
129├─ android.widget.TextView instance
130│    ↓ TextView.mContext
131╰→ com.example.leakcanary.MainActivity instance
132```
133
134Here's how to read that leak trace:
135
136> The `FontsContract` class is a system class (see `GC Root: System class`) and has an `sContext` static field which references an `ExampleApplication` instance which has a `leakedViews` field which references an `ArrayList` instance which references an array (the array backing the array list implementation) which has an element that references a `TextView` which has an `mContext` field which references a destroyed instance of `MainActivity`.
137
138LeakCanary highlights all references suspected of causing this leak using ~~~ underlines. Initially, all references are suspect:
139
140```
141┬───
142│ GC Root: System class
143144├─ android.provider.FontsContract class
145│    ↓ static FontsContract.sContext
146│                           ~~~~~~~~
147├─ com.example.leakcanary.ExampleApplication instance
148│    Leaking: NO (Application is a singleton)
149│    ↓ ExampleApplication.leakedViews
150│                         ~~~~~~~~~~~
151├─ java.util.ArrayList instance
152│    ↓ ArrayList.elementData
153│                ~~~~~~~~~~~
154├─ java.lang.Object[] array
155│    ↓ Object[].[0]
156│               ~~~
157├─ android.widget.TextView instance
158│    ↓ TextView.mContext
159│               ~~~~~~~~
160╰→ com.example.leakcanary.MainActivity instance
161```
162
163Then, LeakCanary makes deductions about the **state** and the **lifecycle** of the objects in the leak trace. In an Android app the `Application` instance is a singleton that is never garbage collected, so it's never leaking (`Leaking: NO (Application is a singleton)`). From that, LeakCanary concludes that the leak is not caused by `FontsContract.sContext` (removal of corresponding `~~~`). Here's the updated leak trace:
164
165```
166┬───
167│ GC Root: System class
168169├─ android.provider.FontsContract class
170│    ↓ static FontsContract.sContext
171├─ com.example.leakcanary.ExampleApplication instance
172│    Leaking: NO (Application is a singleton)
173│    ↓ ExampleApplication.leakedViews
174│                         ~~~~~~~~~~~
175├─ java.util.ArrayList instance
176│    ↓ ArrayList.elementData
177│                ~~~~~~~~~~~
178├─ java.lang.Object[] array
179│    ↓ Object[].[0]
180│               ~~~
181├─ android.widget.TextView instance
182│    ↓ TextView.mContext
183│               ~~~~~~~~
184╰→ com.example.leakcanary.MainActivity instance
185```
186
187The `TextView` instance references the destroyed `MainActivity` instance via it's `mContext` field. Views should not survive the lifecycle of their context, so LeakCanary knows that this `TextView` instance is leaking (`Leaking: YES (View.mContext references a destroyed activity)`), and therefore that the leak is not caused by `TextView.mContext` (removal of corresponding `~~~`). Here's the updated leak trace:
188
189```
190┬───
191│ GC Root: System class
192193├─ android.provider.FontsContract class
194│    ↓ static FontsContract.sContext
195├─ com.example.leakcanary.ExampleApplication instance
196│    Leaking: NO (Application is a singleton)
197│    ↓ ExampleApplication.leakedViews
198│                         ~~~~~~~~~~~
199├─ java.util.ArrayList instance
200│    ↓ ArrayList.elementData
201│                ~~~~~~~~~~~
202├─ java.lang.Object[] array
203│    ↓ Object[].[0]
204│               ~~~
205├─ android.widget.TextView instance
206│    Leaking: YES (View.mContext references a destroyed activity)
207│    ↓ TextView.mContext
208╰→ com.example.leakcanary.MainActivity instance
209```
210
211To summarize, LeakCanary inspects the state of objects in the leak trace to figure out if these objects are leaking (`Leaking: YES` vs `Leaking: NO`), and leverages that information to narrow down the suspect references. You can provide custom `ObjectInspector` implementations to improve how LeakCanary works in your codebase (see [Identifying leaking objects and labeling objects](recipes.md#identifying-leaking-objects-and-labeling-objects)).
212
213## 3. Find the reference causing the leak
214
215In the previous example, LeakCanary narrowed down the suspect references to `ExampleApplication.leakedViews`, `ArrayList.elementData` and `Object[].[0]`:
216
217```
218┬───
219│ GC Root: System class
220221├─ android.provider.FontsContract class
222│    ↓ static FontsContract.sContext
223├─ com.example.leakcanary.ExampleApplication instance
224│    Leaking: NO (Application is a singleton)
225│    ↓ ExampleApplication.leakedViews
226│                         ~~~~~~~~~~~
227├─ java.util.ArrayList instance
228│    ↓ ArrayList.elementData
229│                ~~~~~~~~~~~
230├─ java.lang.Object[] array
231│    ↓ Object[].[0]
232│               ~~~
233├─ android.widget.TextView instance
234│    Leaking: YES (View.mContext references a destroyed activity)
235│    ↓ TextView.mContext
236╰→ com.example.leakcanary.MainActivity instance
237```
238
239`ArrayList.elementData` and `Object[].[0]` are implementation details of `ArrayList`, and it's unlikely that there's a bug in the `ArrayList` implementation, so the reference causing the leak is the only remaining reference: `ExampleApplication.leakedViews`.
240
241## 4. Fix the leak
242
243Once you find the reference causing the leak, you need to figure out what that reference is about, when it should have been cleared and why it hasn't been. Sometimes it's obvious, like in the previous example. Sometimes you need more information to figure it out. You can [add labels](recipes.md#identifying-leaking-objects-and-labeling-objects), or explore the hprof directly (see [How can I dig beyond the leak trace?](faq.md#how-can-i-dig-beyond-the-leak-trace)).
244
245
246!!! warning
247    Memory leaks cannot be fixed by replacing strong references with weak references. It's a common solution when attempting to quickly address memory issues, however it never works. The bugs that were causing references to be kept longer than necessary are still there. On top of that, it creates more bugs as some objects will now be garbage collected sooner than they should. It also makes the code much harder to maintain.
248
249
250What's next? Customize LeakCanary to your needs with [code recipes](recipes.md)!
251