This article mainly introduces the following two topics:

Memory leak detection method: Detects potential memory leaks through leakary &MAT. Memory leak solutions: Common memory leak scenarios and solutions, how to avoid writing leaking code.

It is long, so you can read it according to your own needs.

You should manage your application’s memory


Random-access memory is a valuable resource in any software development environment, but it is even more valuable for mobile operating systems where physical memory is often limited. Although both the Android Runtime (ART) and Dalvik VIRTUAL machines perform regular garbage collection (GC), this does not mean that you can ignore when and where your application allocates and frees memory. You still need to avoid introducing memory leaks.

Memory Overflow (OOM) and Memory Leak (Leak)


Memory overflow (OutOfMemoryError)

To allow multiple processes, Android sets a hard limit on the size of the heap allocated per application. The exact heap size limit varies depending on how much total memory the device has. If your application reaches this limit and tries to allocate more memory, an OutOfMemoryError is thrown.

Memory Leak

An application cannot release the allocated memory space after applying for memory, wasting memory resources. In the worst case, a memory leak can eventually lead to a memory overflow.

Hazards of memory leaks


A memory leak isn’t bad, but it shouldn’t be left alone. In the worst case, your APP could run out of memory due to a large number of memory leaks and crash, but that’s not always the case. Conversely, a memory leak can consume a large amount of memory, but not run out, and the APP will frequently trigger GC due to insufficient memory allocation. GC is a very time consuming operation and can lead to severe lag. In addition, when your application is in the LRU list (that is, switching to the background and becoming a background process), it consumes more memory due to memory leaks, and your application is more likely to be killed by the system when the system is running out of resources and needs to reclaim some of the cache processes.

Tips: The less memory an application consumes in the entire LRU list, the better its chances of staying in the list and being able to recover quickly.

LeakCanary


LeakCanary is a well-known memory leak detection tool. It is easy to use and integrated to warn when an application leaks and display the stack information that has leaked. The new version also shows the exact size of the leaked memory, which is very effective as a passive monitoring tool for leaks. Not being able to provide more detailed memory snapshot data and having to be embedded in the project will pollute the code to a certain extent, so it is generally only integrated in build version and should be removed in Release version.

This article is not about LeakCanary, so we won’t go into the details here, but it is highly recommended that you check out the following blog, which is written by the developers of LeakCanary about how LeakCanary came to be implemented, with a simple and witty explanation of how LeakCanary works:

Use LeakCanary to detect memory leaks

The principle of LeakCanary

While this article isn’t about LeakCanary, I’m curious about how it works. Here’s a quick summary of how LeakCanary works:

  1. Listen for the Activity life cycle and call refwatcher.watch (Activity) when onDestroy is called to check for leaks.
  2. Refwatcher.watch () creates a KeyedWeakReference to the object to be monitored.

    KeyedWeakReference is a subclass of WeakReference, but a key and name are attached as member variables to facilitate the follow-up search of the KeyedWeakReference object. When creating KeyedWeakReference in this step, a construction method of WeakReference is usedWeakReference(T referent, ReferenceQueue<? super T> q)This constructor is crucial and will be used in the next step.
  3. The background thread then checks to see if the reference is cleared and, if not, invokes GC. How exactly do you check? This is due to the last step to construct KeyedWeakReference object when the ReferenceQueue, about this class, interested in can directly see the source code of Reference. What we need to know here is that every time the object that WeakReference points to is GC, the WeakReference will be put into the ReferenceQueue queue associated with it. So at this time we go to check the ReferenceQueue, if there is no KeyedWeakReference, then the object it points to is likely to leak, but in order to prevent false positives, LeakCanary will do a secondary GC confirmation, that is, actively trigger a GC.
  4. If the reference is still not cleared, the heap memory is dumped to an.hprof file in the corresponding file system of the APP.
  5. The HeapAnalyzerService in another process has a HeapAnalyzer that uses HAHA to parse this file.
  6. Thanks to the unique Reference key, HeapAnalyzer finds the KeyedWeakReference and locates the memory leak.
  7. HeapAnalyzer calculates the shortest strong reference path to GC roots and determines if it is a leak. If so, establish the chain of references that caused the leak.
  8. The chain of references is passed to the DisplayLeakService in the APP process and displayed as a notification.

For a more detailed analysis of the LeakCanary source code, see the following blog:

LeakCanary Research on the principle of memory leak detection

Of course, LeakCanary has more advanced uses, such as the ability to add ignore (some third-party libraries or even the Android SDK itself leak you may not be able to fix, but you don’t want LC to always alert), the ReferenceWatcher to monitor specific classes, and more, see its GitHub documentation:

LeakCanary on GitHub

The author used LeakCanary as a monitoring tool and MAT as an analysis tool. MAT is more powerful than LeakCanary, and of course more complex to use, providing detailed in-memory analysis data that does not need to be embedded in the project. Here is how to use MAT.

Introduction of MAT


The MAT(Memory Analyzer Tool), an Eclipse-based Memory analysis Tool, is a fast, feature-rich JAVA Heap analysis Tool that helps us find Memory leaks and reduce Memory consumption. Use the memory analysis tool to analyze from many objects, quickly calculate the size of the object in memory, see who is blocking the garbage collector’s recycling work, and can intuitively view the possible result of the object through the report.

In addition to the Eclipse plug-in version, MAT also has a separate version that does not rely on Eclipse. However, when debugging Android memory, this version needs to convert the files generated by DDMS to be opened on the independent version of MAT. Because DDMS generates HPROF (heap dump) files in Android format, MAT can only recognize HPROF files in JAVA format. But the Tools is already available in the Android SDK, so it’s easy to use.

To debug memory, you first need to get the HPROF file, which stores a snapshot of the memory of the Java process at a particular point in time. There are different formats for storing this data, which generally include Java objects and classes in the heap when the snapshot is triggered. Since snapshots are only instantaneous, heap dump cannot contain information about when and where (in which method) an object was allocated.

Official documentation Basic Tutorial

Introduction to some concepts in MAT


Shallow heap, Retained heap, and GC Root are important concepts to understand in order to understand MAT’s list.

Shallow heap

Shallow size is the size of the memory occupied by the object itself, excluding the objects it references.

  • The Shallow size of a normal object (not an array) is determined by the number and type of its member variables.
  • The shallow size of an array is determined by the type of the array element (object type, primitive type) and the array length.

Because unlike c++ objects, which can store a lot of memory themselves, Java object members are references. The real memory is on the heap, which looks like a bunch of native byte[], char[], int[], so if we only look at the memory of the object itself, the amount is very small. Therefore, we see that the Histogram graphs are sorted by Shallow size, and the first and second positions are generally byte and char.

Retained Set

Retained Set refers to the collection of all Retained objects that were removed from GC as a result of the release of X when an object X was GC.

Retained Heap

Retained Heap refers to the sum of the Shallow sizes of all the Retained sets of an object X. In other words, the Retained Heap represents the total Heap size that would be freed if an object were freed. Thus, if a member of an object new a large array of ints, that array of ints can also be evaluated to the object. Retained heap can reflect the size of an object more accurately than its shallow heap (because the Retained heap can be freed if the object is Retained).

As a reminder, Retained Heap doesn’t always work. Let’s say I new A block of memory in A and assign it to A member variable of A. And now I want B to point to this memory as well. At this point, since both A and B reference this block of memory, the memory will not be freed when A is freed. So this piece of memory is not counted on the Retained Heap of A or B. To correct this, the Leading Object (such as A or B) in MAT is not only one Object, but can also be multiple objects (Leading Set). Retained Set (A, B) includes that large piece of memory.

Obviously, calculating Retained Memory from the above object reference graph is not intuitive and efficient. Retained Memory of A and B is only themselves, but E Retained Memory is E and G, and G Retained Memory is only itself. Across Memory, MAT introduces the concept of Dominator Tree.

Dominator Tree

In the Dominator Tree, there are some informal definitions:

  • In an object graph, if every path from the starting node (or root node) to node Y must pass through node X, then node X is a boronode Y.
  • In a Dominator Tree, each node is the direct Dominator of its children.

The Dominator Tree also has the following important attributes:

  • The sub-tree of X represents the retained set of X.
  • If X is the immediate Dominator of Y, then the immediate Dominator of X is similarly boroy, and so on.
  • The edges of the Dominator Tree do not directly correspond to reference relationships in the object reference Tree. For example, in the reference diagram, C is A child of A and B, whereas in the Dominator Tree, all three are level.

On the MAT UI, the shallow heap and retained heap for each object are displayed in the Dominator Tree view. By right-clicking, you can select with Outgoing References and With Incoming References from List Objects. This is a real reference graph concept,

  • Outgoing References: indicates the outgoing node of this object (the object referenced by this object).
  • Incoming References: Indicates the incoming node of this object (the object that references this object).

GC Roots

First, let’s talk about the principles of GC:

The garbage collector tries to reclaim all non-GC Roots objects. So if you create an object and remove all references to that object, it will be recycled. But if you set an object to GC Roots, it will not be recycled. How does the GC determine if an object can be reclaimed? During garbage collection, when an object has no chain of references to GC Roots (or is unreachable from GC Roots to the object), the garbage collector releases it.

GC Roots are objects kept alive by the virtual machine itself. Such as running threads, objects currently in the call stack, classes loaded by the System Class Loader, and so on.

In turn, the path from an object to a GC Root explains why the object cannot be GC. This path can help solve a typical memory leak.

A GC root is an object that can be accessed and read from outside the heap. There are some ways to make an object gc root:

  • System class: classes loaded by Bootstrap or System class loader, such as all classes in rt.jar (such as java.util.*);
  • JNI local: local variables in native code, such as user-defined JNI code and JVM internal code;
  • JNI Global: global variables in native code, such as user-defined JNI code and JVM internal code;
  • Thread block: objects referenced in the currently active Thread block;
  • Thread: threads that have been started and have not been stopped;
  • Busy monitor: Objects that call wait() or notify() or are synchronized, such as synchronized(Object) or synchronized methods. Static methods refer to classes, non-static methods refer to objects;
  • Java Local: Local variables, such as method inputs and objects created within methods that still exist in the thread stack;
  • Native stack: the input and output parameters of native code, such as file/net/IO methods and reflection parameters;
  • Finalizable: an object ina queue waiting for its Finalizer to run;
  • Unfinalized: an object with finalize method, which has not yet been Finalized, also does not enter the Finalizer queue to wait for Finalize.
  • Unreachable: objects that are marked by MAT as root and cannot be reached by any other root. This root is used to retain objects that would not otherwise be included in the analysis.
  • Java Stack Frame: A Java stack frame that holds local variables. Generated only if dump is parsed and stack frames are treated like objects in preferences;
  • Unknown: Indicates the unknown object of the root type.

Java reference level

From the strongest to the weakest, different levels of reference (reachability) reflect the life cycle of the object.

  • Strong referenceThrough:newAll object references created by keyword are strong references. Objects can be reclaimed only when strong references are removed. Please remember,The JVM would rather throw OOM than reclaim an object with a strong reference.
  • Soft reference: Objects are held as long as there is enough memory, until the system finds that there is still insufficient memory after one GC, and the system will recycle such objects twice before OOM occurs. An OOM is thrown if there is not enough memory to recycle. Generally can be used to implement caching, through the Java. Lang. Ref. SoftReference class implements.
  • Weak references: Weaker than Soft Ref, objects associated with weak references will only survive until the next GC occurs. At GC execution, objects associated only with weak references are immediately reclaimed regardless of whether there is currently enough memory. Through the Java. Lang. Ref. WeakReference and Java. Util. WeakHashMap class implements.
  • Virtual references: Also known as ghost references or phantom references. Virtual references do not affect the lifetime of the object at all, you can only use the Phantom Ref itself, not get an object instance through a virtual reference. The only purpose of setting a virtual reference association for an object is to receive a system notification when the object is GC. Are commonly used in entering the finalize () method after special cleaning process, through the Java. Lang. Ref. PhantomReference implementation.

Get the HPROF (heap dump) file


HPROF (heap dump) files can be exported using DDMS. In Android Studio, choose Tools → Android → Android Device Monitor (this button can be fixed in the AS toolbar panel for ease of use). DDMS has a row of buttons on top of Devices. After selecting a process (i.e. selecting the package name of the application you want to debug from the list listed below Devices), click the Dump HPROF file button:

The HPROF file of the corresponding process can be obtained after selecting the storage path. However, this file is in Android format, you can directly drag into AS to browse, but the functionality is limited, to do more in-depth memory analysis, usually use a specialized analysis tool, such AS Eclipse MAT or Oracle Jhat (jdK6 later provided). MAT comes in two versions: the Eclipse plug-in version and the client version, and the plug-in version does this with one click. Simply click the Dump HPROF file icon and the MAT plug-in will automatically convert the format and open the analysis results in Eclipse. Eclipse also has a dedicated Memory Analysis view. Once you have the files, switch to the Memory Analyzer view if you have the Eclipse plug-in installed.

If MAT is installed independently, the tool delivered with Android SDK (hprof-conv is located in SDK /platform-tools/hprof-conv) shall be used to convert the above Hprof file in Android format to hprof file in Java format:

Hprof-conv [-z] com.test.myproject.hprof.test.myProject_conv.hprof-z: Exclude interference from non-APP leaks, such as zygoteCopy the code

(Windows system may need to go to the above path to find hprof-conv.exe and install it once). Hprof file after conversion can be opened by using MAT tool.

Tips: Before AS3.0, it was the Android Monitor (the TAB next to the Logcat window). After AS3.0, it was the Android Profiler. And they can also do some memory analysis.

MAT generally uses steps


1. Open the converted hprof file:

If the first one is selected, a report is generated. This does not matter, is the tool to help you analyze the suspected leak of the object, can be used as a quick reference.

2. Select the OverView screen:

The “Unreachable Objects Histogram” above refers to Objects that are currently available for GC, but are still alive in the heap because the system has not yet triggered GC. This is generally not a concern.

The common methods are Histogram and Dominator Tree.

Histogram

Lists how many instances are assigned to each class, as well as their size.

The first two characters are byte[] and char[]. Retained Heap is not displayed by default in the Histogram view. To view the size of Retained Heap, click the button in the toolbar:

To facilitate viewing and quickly find problems with your own class, you can “Group by package” :

Dominator Tree

The largest Object and its dependent Object (size is Retained Heap) are listed with a column of Percentage.

You can also “Group by package”

We see that MobUIShell itself occupies 408 bytes of memory, and if it were reclaimed, it would free up 20,840 bytes of memory space.

Merge Shortest Paths to GC Roots – Exclude weak/soft References Weak /soft References do not normally cause memory leaks.

As you can see, SizeHelper leaks an instance of MobUIShell, which itself occupies 200 bytes of memory, while it also holds 201,160 bytes of memory for other objects. In other words, once it is cleaned up, it can free up about 200K of memory.

This is consistent with the LeakCanary report:

At this point, we know that SizeHelper leaks MobUIShell. The next step, of course, is to analyze why it leaks and how to fix it.

Public class SizeHelper {public static float designedDensity = 1.5f; public static int designedScreenWidth = 540; private static Context context = null; protected static SizeHelper helper; private SizeHelper() { } public static void prepare(Context c) { if(context == null || context ! = c.getApplicationContext()) { context = c; } } public static int fromPx(int px) { return ResHelper.designToDevice(context, designedDensity, px); } public static int fromPxWidth(int px) { return ResHelper.designToDevice(context, designedScreenWidth, px); } public static int fromDp(int dp) { int px = ResHelper.dipToPx(context, dp); return ResHelper.designToDevice(context, designedDensity, px); }}Copy the code

The code above is one of the most common and simplest types of leak-caused by static variables. Static global context:

private static Context context = null;Copy the code

How to fix the leak here? Three options:

Solution a: While you can avoid leakage by passing an Application Context instead of an Activity Context to the prepare method, you can’t guarantee that someone else will not pass an Activity Context when using SizeHelper. So it’s not desirable. To root out leaks, you must refactor your code.

Option 2: Refactor the code to force SizeHelper to hold only instances of ApplicationContext.

public static void prepare(Context c) { if(context == null || context ! = c.getApplicationContext()) {// Force the ApplicationContext from the Context, Let SizeHelper hold an instance of ApplicationContext context = c.goetApplicationContext (); }}Copy the code

The solution still allows you to modify the context with the static keyword, and while the context is still not GC, it itself holds only an ApplicationContext instance, not an Activity context instance, so it doesn’t leak an Activity. However, in rare cases where this scheme is not available, such as when you can only use the Activity Context (and not the ApplicationContext when Dialog is related), you have to take a different approach, such as scheme 3.

Scenario 3: Refactor the code so that it no longer holds Context instances.

Public class SizeHelper {public static final float designedDensity = 1.5f; public static final int designedScreenWidth = 540; public static int fromPx(Context context, int px) { return ResHelper.designToDevice(context, designedDensity, px); } public static int fromPxWidth(Context context, int px) { return ResHelper.designToDevice(context, designedScreenWidth, px); } public static int fromDp(Context context, int dp) { int px = ResHelper.dipToPx(context, dp); return ResHelper.designToDevice(context, designedDensity, px); }}Copy the code

Context is no longer set as a static global variable, but is used as a local variable within a method, so it looks like SizeHelper itself is no longer likely to leak, but it’s actually a method that calls ResHelper, so we need to make sure ResHelper doesn’t leak. Here is part of the code for ResHelper:

public class ResHelper { private static float density; private static int deviceWidth; private static Object rp; private static Uri mediaUri; public ResHelper() { } ... public static int designToDevice(Context context, float designScreenDensity, Int designPx) {if(density <= 0.0f) {density = context.getResources().getDisplayMetrics(); } return (int)((float)designPx * density/designScreenDensity + 0.5f); }... }Copy the code

ResHelper is a normal class that provides a set of static methods, and it does not store the Context as a static global variable, so it does not leak the Context object.

Ok, let’s run it again to see MAT’s analysis:

MobUIShell’s Retained Heap is now only 680bytes, and its memory percentage is down to 0.01%. The released memory is 201840byte-680byte =201160Byte (which is exactly the same as the data MAT reported when detecting this leak in the previous chart). Although it is not completely resolved, the remaining part is obviously caused by Android SDK’s InputMethodManager. It has nothing to do with SizeHelper anymore.

For a more quick comparison, from the Histogram page, by clicking on the “Compare to another Heap Dump” button and selecting a comparison with the previous Heap Dump, we see that MobUIShell’s Shallow Heap compared to its previous Heap Dump, A reduction of 200 bytes, and an application-wide memory leak of 213224 bytes (around 200K). Note that in the following figure, the Heap Dump after modification is compared with the Heap Dump before modification, so the value is displayed as -200. If the Heap Dump before the change is compared with the Heap Dump after the change, 200 is displayed, which means that the memory before the change is 200 bytes more than the memory after the change.


You can also find responsible objects through the Immediate Dominator, which is useful for quickly locating the owner of a set of objects. This action resolves the “who keeps objects alive” problem rather than the “who has references to objects” problem, which is more direct and efficient.

Common memory leaks and solutions


Through the introduction of the above chapters, we learned how to use MAT to analyze memory leaks. This chapter mainly introduces several common memory leaks and their solutions. Before we do that, let’s learn a little more about memory leaks in Android.

Traditional memory leaks are caused by forgetting to release allocated memory, such as forgetting to close after using up a Stream or DB Connection, while Logical leaks are caused by forgetting to release references to an object when it is no longer in use. If an object still has strong references, the garbage collector cannot collect it. Leaking Context objects is especially problematic on Android. This is because the Acitivity points to the Window, which has the entire View inheritance tree, and the Activity may also reference other resources (such as bitmaps) that consume a lot of memory. If the Context object has a memory leak, all the objects it references are leaked.

If the reasonable life cycle of an object is not clearly defined, then determining a logical memory leak is a matter of opinion. Fortunately, activities have a clear lifecycle definition that makes it very clear if an activity object is leaking memory. The onDestroy() function is called when the activity is destroyed, either actively by the programmer or by the system to reclaim memory. If the activity object is still strongly referenced by heap root after onDestroy completes, the garbage collector cannot reclaim it. So we can define leaked activities as activities that are referenced after the end of their life cycle.

An Activity is a very heavyweight object, so it should be avoided to prevent the system from recycling it. However, there are several ways we can inadvertently leak activity objects. There are two main categories that can cause activity leaks. One is static variables that use process-global and persist regardless of the state of the APP. They hold strong references to the activity and cause memory leaks. The other is threads with a lifetime longer than the activity, which forget to release strong references to the activity and cause memory leaks. Let’s take a closer look at each of these situations that can cause your activity to leak.

Tips: Why do static variables cause leaks? This should speak of from the Java foundation, static modified called static variables, also known as the class variables and class variables can be seen from the name can lifecycle is binding on the object class (class), rather than a specific instance of the class object life cycle from the class loader to load until the end of the application, is almost equal to the application of life cycle. So once a static variable holds a strong reference to the Activity, it causes a leak.

Static Activity

private static Context context;
proteced void onCreate(Bundle savedInstanceState) {
    this.context = this;
}Copy the code

Avoid using the static keyword for context. If you do use it, you must ensure that the context is only ApplicationContext and not an Activity context. That is to combine the following code:

this.context = context.getApplicationContext();Copy the code

Or clear this reference before the Activity life cycle ends:

protected void onDestroy() {
    this.context = null;
}Copy the code

Static View

Sometimes we might have a View that is very time-consuming to create and remains constant throughout the life cycle of the same activity, so let’s implement a singleton pattern for it.

private static View view; void setStaticView() { view = findViewById(R.id.sv_button); } View svButton = findViewById(R.id.sv_button); svButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); }});Copy the code

You’ve leaked your Activity again! Because once the view is added to the interface, it holds a strong reference to the context, which is our activity. Since we refer to the View through a static member, we also refer to the Activity, so the activity leaks. So never assign a loaded view to a static variable, and if you do, be sure to remove it from the View hierarchy before the activity is destroyed.

void removeView (View view)Copy the code

The singleton

There is nothing wrong with a singleton, but if you use a context object as a global variable in a singleton, you will leak.

class Singleton { private static Singleton instance; private Context context; private Singleton(Context context) { this.context = context; } public static Singleton getInstance(Context context) { if (instance == null) { instance = new Singleton(context); } return instance; }}Copy the code

The above code is a thread-unsafe singleton pattern, but it does not affect our analysis of leaks caused by singletons.

Likewise, either ensure that the context can only be ApplicationContext, or do not write the context as a global variable.

The construction method can be modified:

private Singleton(Context context) {
    this.context = context.getApplicationContext();
}Copy the code

Of course, if the singleton is associated with a Dialog, then you cannot use ApplicationContext and have to refactor the code instead of writing the context as a global variable.

Non-static inner class

Inner classes are often used in programming for a number of reasons, such as increased encapsulation and readability. An activity leak also occurs if we create an object of an inner class and hold a reference to that inner class object through a static variable.

private boolean b = false; private static InnerClass inner; void createInnerClass() { inner = new InnerClass(); } class InnerClass { private boolean bool; public InnerClass() { this.bool = b; }}Copy the code

One of the advantages of inner classes is the ability to refer directly to members of an outer class by implicitly holding references to the outer class, which is exactly what causes activities to leak.

When using a non-static inner class, it is important to pay attention to the lifetime of the reference, so that the inner class does not have a lifetime beyond the outer class.

private InnerClass inner;Copy the code

However, in real development, we should still try to avoid using non-static inner classes and use static inner classes instead, because static inner classes do not hold references to the outer class and thus do not leak the outer class, but static inner classes cannot access the members of the outer class. If your code structure must access a member of an external class, then use static inner class + weak reference. Let the static inner class hold a weak reference of the external class. This will not cause leakage and will solve the problem of accessing the member variables of the external class.

private boolean b = false; private static InnerClass inner; void createInnerClass() { inner = new InnerClass(this); } static class InnerClass {private Boolean bool; private WeakReference<MainActivity> activityWeakReference; Public InnerClass(MainActivity activity) {activityWeakReference = new WeakReference<>(activity); public InnerClass(MainActivity activity) {activityWeakReference = new WeakReference<>(activity); this.bool = activityWeakReference.get().b; // So access the member of the external class}}Copy the code

Anonymous inner class

Anonymous inner classes cause memory leaks in the same way that non-static inner classes do, because anonymous inner classes also implicitly hold references to external classes. A typical scenario for Android development is to use a Handler. Many developers write this when using a Handler:

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 1) { // }}}; }Copy the code

MHandler does not hold a reference to the Activity as a static variable. It probably doesn’t last longer than the Activity and should not cause a memory leak.

This starts with the Handler message mechanism. MHandler is stored as a member variable in the sent message MSG, which holds a reference to mHandler, which is a non-static internal class instance of the Activity. The MSG indirectly holds a reference to the Activity. MSG is sent to MessageQueue, and then waits for the polling process of Looper (MessageQueue and Looper are associated with the thread, MessageQueue is the member variable referenced by Looper, Looper is stored in ThreadLocal). When the Activity exits, MSG may still be unprocessed or being processed in the MessageQueue MessageQueue, which causes the Activity to be unable to be reclaimed.

Applying the “static inner class + weak reference” approach, refactor the code:

public class MainActivity extends AppCompatActivity { private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start(); } private void start() { Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); } private static class MyHandler extends Handler { private WeakReference<MainActivity> activityWeakReference; public MyHandler(MainActivity activity) { activityWeakReference = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { MainActivity activity = activityWeakReference.get(); if (activity ! If (msg.what == 1) {// do the corresponding logic}}}}}Copy the code

MHandler holds the Activity by weak reference, and when the GC performs garbage collection, the Activity is reclaimed and freed. That way, there will be no memory leaks.

The above does prevent Activity leaks. The sent MSG no longer holds a strong reference to the Activity, but the MSG may still be in the MessageQueue MessageQueue, so it is better to remove the mHandler callback and the sent message when the Activity is destroyed.

@Override
protected void onDestroy() {
    super.onDestroy();
    mHandler.removeCallbacksAndMessages(null);
}Copy the code

Let’s take another look at a very common scenario: timers implemented with handlers. For example, after sending an SMS verification code, there is usually a 1-minute countdown before it can be sent again. If the timer is not actively shut down at the appropriate time and the timer indirectly holds a reference to the activity context, the activity will be leaked before the timer ends.

The following is a countdown timer implemented in the SMS GUI using Handler:

private void countDown() { runOnUIThread(new Runnable() { public void run() { time--; setResendText(time); if (time <= 0) { time = 60; } else { runOnUIThread(this, 1000); }}}, 1000); }Copy the code

Where runOnUIThread is a method in the FakeActivity class:

public void runOnUIThread(final Runnable r, long delayMillis) { UIHandler.sendEmptyMessageDelayed(0, delayMillis, new Callback() { public boolean handleMessage(Message msg) { r.run(); return false; }}); }Copy the code

Callback is an anonymous inner class of FakeActivity that holds a strong reference to the external class FakeActivity, which in turn holds a strong reference to the actual Activity context, so before the timer stops, The current page will be leaked. The solution to this problem is to actively stop the timer when you leave the current page:

@Override public void onDestroy() { super.onDestroy(); // Leave the page before stopping the timer stopCountDown(); } private void stopCountDown() { time = 1; }Copy the code

Note that not only does the timer stop in onDestroy, but it also stops when you enter the correct verification code and jump to the next page (onDestroy is not called at this point)!

There are many other scenarios where anonymous inner classes cause leakage, such as defining an anonymous AsyncTask in an Activity. If an Activity ends without terminating the AsyncTask properly, then the GC can’t reclaim the Activity until the AsyncTask has finished executing. Similarly, Threads and TimerTasks created through anonymous inner classes are likely to leak activities because they don’t end properly. In addition, common listener and callback objects (whether implemented by an internal class or by making the Activity directly implements the callback) can leak activities. Instances of these Callback will most likely end up being held by a class variable (such as a singleton) or a thread with a long life cycle through multiple references, causing the Activity to leak. That’s what happens with our AsyncImageView, which is our custom View that holds the activity context itself, and in order to process the image, Internally, a Callback object is created through the anonymous inner class to be passed to the BitmapProcess class to receive the image processing results, and the BitmapProcess passes several more times to store the Callback object in a static ArrayList object. To fix this, the reference to Callback must be cleared (set to NULL) after it is used.

SensorManager and broadcast receivers

System services can be obtained through context.getSystemService, which perform some background tasks or provide interfaces for hardware access. If the Context object wants to be notified when events occur within the service, it needs to register itself with the listener of the service. However, this leaves the service holding a reference to the activity, which can leak if the programmer forgets to unregister the activity when it is destroyed.

void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL); sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST); } View smButton = findViewById(R.id.sm_button); smButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { registerListener(); nextActivity(); }});Copy the code

The same goes for registering broadcasts, and forgetting to unregister a broadcast receiver while your Activity is being destroyed can cause your Activity to leak.

Objects in the collection are not cleaned up causing a memory leak

This is easy to understand. If an object is put into a collection of ArrayList, HashMap, etc., the collection holds a reference to that object. When we no longer need this object, we do not remove it from the collection, thus causing a memory leak as long as the collection is still in use (and the object is no longer useful). And if the collection is statically referenced, unused objects in the collection will cause memory leaks. Therefore, when using the collection, it is necessary to remove or clear the unused objects from the collection in time to avoid memory leaks.

Resources are not shut down or released, resulting in memory leaks

Close it when using IO, File streams, or RESOURCES such as Sqlite and Cursor. These resources usually use buffers for read and write operations. If they are not closed in time, these buffer objects will remain occupied and cannot be released, resulting in memory leaks.

Property animation causes a memory leak

Animation can also be a time-consuming task. For example, ObjectAnimator is started in an Activity, but the cancle method is not called when it is destroyed. Although we can no longer see the animation, the animation will continue to play. The control referenced the Activity, which causes the Activity to not be released properly. Therefore, cancel the property animation when the Activity is destroyed to avoid memory leaks.

@Override
protected void onDestroy() {
    super.onDestroy();
    mAnimator.cancel();
}Copy the code

WebView causes memory leaks

As for WebView memory leaks, the WebView takes up memory for a long time after loading the web page and cannot be freed, so we call its destroy() method after the Activity is destroyed to free memory.

WebView memory leak:

The Callback below the Webview holds the Activity reference, causing the Webview memory to fail to be freed, even if methods such as webview.destory () are called (after Android5.1).

The final solution is to remove the WebView from the parent container before destroying the WebView. Refer to WebView Memory Leak Resolution for a detailed analysis of the process

@Override protected void onDestroy() { super.onDestroy(); / / removed from the parent control first WebView mWebViewContainer. RemoveView (mWebView); mWebView.stopLoading(); mWebView.getSettings().setJavaScriptEnabled(false); mWebView.clearHistory(); mWebView.removeAllViews(); mWebView.destroy(); }Copy the code

Summary: How to avoid writing leaky code


  1. Be careful with the static keyword, especially when modifying the Activity context with static;
  2. Be careful not to let a class variable directly or indirectly hold a reference to the Activity context;
  3. Try not to use the Activity context in a singleton. If you do, do not use it as a global variable.
  4. Pay attention to the life cycle of an inner class (especially an Activity’s inner class). Try to use a static inner class instead of an inner class. If an inner class needs to access a member of an outer class, use “static inner class + weak reference” instead. The life cycle of the inner class should not exceed that of the outer class. Before the end of the outer class, the inner class life cycle should be ended in time (stop the thread, AsyncTask, TimerTask, Handler message, etc., remove the strong reference of class variables or threads with long life cycle to Callback, Listener, etc.).
  5. Timely logout of broadcast and some system service listeners;
  6. The property animation remembers cancel before the Activity is destroyed;
  7. Close the file flow, Cursor and other resources when they are used up.
  8. Remove and destroy WebView before Activity destruction;
  9. Using someone else’s methods (especially third-party libraries), use ApplicationContext when you need to pass a context, rather than using an Activity Context because you don’t know if someone else’s code will leak the context. For example, wechat Pay SDK has the potential to leak, wechat Pay initialization needs to pass in the context, the final WXApiImpl class holds the context, if you pass in an activity context, WXApiImpl will leak.

Knowledge combing


1. How does GC determine if an object can be reclaimed:

During garbage collection, when the total number of strong references to an object is zero (that is, no strong references exist), the garbage collector releases it.

2.Java reference level:

Strong reference – Soft reference – Weak reference – Virtual reference

3. The JVM would rather throw OOM than reclaim a strongly referenced object
4. The GC Root:

There are several ways to make an object GC Root. GC Root is an object kept alive by the virtual machine itself, so it cannot be reclaimed, and objects strongly referenced by GC Root cannot be reclaimed.

Inner classes and static inner classes:

One advantage of inner classes is that they can refer directly to members of an outer class by implicitly holding references to the outer class. Static inner classes, because they no longer implicitly hold references to the outer class, can no longer refer directly to the members of the outer class.

6. How to avoid leakage caused by internal classes:

To prevent inner classes from leaking outer classes, static inner classes should be used. However, the static inner class cannot access the member of the external class. To solve this problem, we can use “static inner class + weak reference”, so that the static inner class holds the weak reference of the external class, which will not cause leakage, and can solve the problem of accessing the member variable of the external class.

7.LeakCanary How to check for a memory leak:

WeakReference + ReferenceQueue

reference


This article is the author did a lot of reference study, and combined with their own practice summary, hereby thank you: LeakCanary on GitHub Uses LeakCanary to detect Memory leaks Overview of Memory Management Vidoe: Memory Management on Android Apps