preface

I haven’t posted my blog for a long time since I started working. A long time ago said to my personal open source library AppInject principle to do an explanation also shelved for a long time… Dig a hole here and fill this one up by November…

Recently, I took over an old module in the project (maybe more than 8 years old), and my classmates in the performance group cleaned out many memory leaks in the modules. Then, my current work is to repair the memory leaks while catching up with the demand (and also make up the single test). Having solved quite a few memory leaks and gained a better understanding of this area, here are some simple summaries.

What is a memory leak?

The essence of memory leakage is that the object that should have been reclaimed is not reclaimed. For example, the Activity object of a closed page is kept in the memory and cannot be released. It accumulates slowly until OOM and crashes. The JVM has a built-in garbage collection (GC) mechanism that does not require manual memory release like C/C++.

Not all objects will be collected during GC. In short, if your object is in use, it will not be collected (otherwise who would dare to use the JVM?). What is being used is determined by reachability analysis for the JVM, that is, from some fixed fixed object that will not be recycled based on the reference relationship, the referenced object will not be recycled. The “fixed and certain objects that will not be recycled” are mainly the following categories:

  • Objects referenced in the virtual machine stack (the local variable table in the stack frame) : that is, local variables in the method, including parameters and some variables defined in the method, belong to GCRoot. Note here that each thread has a stack, not just the main thread.
  • Object referenced by class static attribute in method area: bystaticModified objects belong to GCRoot and are not recycled.
  • Objects referenced by constants in the method area: mainly class file information and constants (strings, values, etc.). This area generally does not cause memory leaks and can be ignored for analysis of memory leaks.
  • Objects referenced by Native methods in the Native method stack: Native methods can be passed by such methods asNewGlobalRefSuch methods make objects GCRoot and are not recycled. This article focuses on the Java layer, not on it.

Analysis of common memory leaks

Now that we know why memory is not freed, we can analyze memory leaks from some common scenarios.

Subscribe to the relationship between

For example, if we want to make a network request, we will see this code:

public class XXActivity extends Activity implements XXListener{
	 // ...

	@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
        xxRepository().addListener(this);
    }
    
    public void doRequest(a){
        xxRepository().request();
    }
    
    @Override
    public void onRequestFinish(XXX data) {
        // update UI...
    }
    
    // ...
    
    
}
Copy the code

The Activity implements XXListener and passes xxRepository. In this case, the xxRepository object, although only the XXListener interface, is actually the Activity object. If the xxRepository object is global (such as a singleton) or has a lifetime longer than the Activity, then when we leave the page, we will leak memory because xxRepository still holds a reference to the Activity. Therefore, this subscription relationship requires that the subscription be unsubscribe at the end of the object’s life cycle, such as onDestroy.

@Override
protected void onDestroy(a) {
    super.onDestroy();
    xxRepository().removeListener(this);
}
Copy the code

Of course, the few memory leaks I’ve addressed with subscriptions have shown calls to unsubscribe, but some were due to multithreaded concurrency not being unsubscribe correctly, and some were due to upper-level lifecycle management problems that didn’t go to the destroy method on the object. The example given here is the simplest case, but everyone should get into the habit of unsubscribing in time during real development.

Anonymous inner class

Here is a direct example:

public class XXActivity extends Activity implements XXListener {
    private View xxView;
    private int id;
    
    // ...
    
    public void doRequest(a) {
        SomeHeavyTaskManager.getInstance().doHeavyTaskAsync(new ICallback() {
            @Override
            public void onResult(Object data) { xxView.updateData(data); doSomothing(id); }}); }// ...
}
Copy the code

The above doHeavyTaskAsync will execute tasks of unknown duration on the worker thread and then throw back to the main thread to call the onResult method of the ICallback passed in. So, there will be a situation where the user has left the page long before the doHeavyTaskAsync callback, the worker thread must be holding a reference to the incoming ICallback, and our XXActivity will be leaked through the anonymous inner class.

This $0 = this$0; this$0 = this$0; this$0 = this$0; this$0 = this$0; this$0 = this$0; this$0 = this$0;

For this kind of leakage, it’s hard to detect. One might think that only xxView is leaking, and pass a weak reference to xxView into ICallback, as shown in the following code. This would not clean up the memory leak, and the XXActivity would still leak from ICallback memory:

public class XXActivity extends Activity implements XXListener {
    private View xxView;
    private int id;

    // ...

    public void doRequest(a) {
        final WeakReference<View> xxViewRef = new WeakReference<>(xxView);
        SomeHeavyTaskManager.getInstance().doHeavyTaskAsync(new ICallback() {
            @Override
            public void onResult(Object data) {
                View view = xxViewRef.get();
                if(view ! =null) { view.updateData(data); } doSomothing(id); }}); }// ...
}
Copy the code

In this case, it is recommended to write the callback as a separate class, or use a static inner class to implement the callback. If the parameters needed in the callback are basic types, they are directly assigned to the callback class. Non-basic types are passed in by deep copy or weak reference as appropriate.

public class XXActivity extends Activity implements XXListener {
    private View xxView;
    private int id;

    // ...

    public void doRequest(a) {
        final WeakReference<View> xxViewRef = new WeakReference<>(xxView);
        SomeHeavyTaskManager.getInstance().doHeavyTaskAsync(new HeavyTaskCallback(xxView, id));
    }

	// Note that it is a static inner class, otherwise the external class instance will still be held
    private static class HeavyTaskCallback implements ICallback {
        private final WeakReference<View> xxViewRef;
        private final int id;

        private HeavyTaskCallback(View xxView, int id) {
            // View has its own life cycle and uses weak references
            this.xxViewRef = new WeakReference<>(xxView);
            // id is a basic type
            this.id = id;
        }

        @Override
        public void onResult(Object data) {
            View view = xxViewRef.get();
            if(view ! =null) { view.updateData(data); } doSomothing(id); }}// ...
}
Copy the code

This way, even if doHeavyTaskAsync is executed in another thread for a long time, only the new HeavyTaskCallback object will be held, and the xxViewRef is a weak reference and will not affect the View’s recycling.

InputMethodManager Memory leaks

This is a more partial thing, will only be in part of the domestic ROM, such as Millet, Huawei Android6, 7 models see more. LeakCanery has found that TextView and its subclasses may be displayed in the following figure after some devices have exited the interfacemNextServerdViewHold.For memory leaks in this case, the solution is situational. If you just leak a TextView, InputMethodManager is singleton, and that object doesn’t matter. However, if the View indirectly holds some other heavy objects, such as multimedia and Activity, then the following two ways should be considered to solve the problem:

  1. (not recommended) by reflection at the end of the life cyclemNextServedViewSet to null.
  2. Disassociate these objects from the View at the end of the lifecyclemNextServedViewThe View).

conclusion

In fact, from the above point of view, the memory leak in Android can be summed up in one sentence:

An object whose life cycle has expired is still referenced by a living object.

The solution to these memory leaks is essentially to sever the relationship between references to objects that have ended their life cycle.

Personally, common when writing code, if you want to give out the current object (set to other objects the Listener/Callback, or referenced by any other means) or internally to write some anonymous inner class or not is a static inner class, can think about the purpose of the current object life cycle and release the object’s lifecycle, Many memory leaks can be avoided directly. In addition, for stale code, you can use tools such as LeakCanary to catch memory leaks in your project.