Android---core principle of memory leak detection

Table of contents

LeakCanary Core Principles

LeakCanary detects the type of object

ReferenceQueue 与 WeakReference

Watchlists and Holdlists in LeakCanary

Common memory leak cases

1. Singletons lead to memory leaks

2. Static variables cause memory leaks

3. Non-static inner class causes memory leak

4. Failure to unregister or callback causes a memory leak

5. Timer and TimerTask cause memory leak

6. The objects in the collection are not cleaned up, causing memory leaks

7. Resources are not closed or released causing memory leaks

8. Property animation causes memory leak

9. WebView causes memory leak


LeakCanary

LeakCanary is an open source library from Square Inc. Through it, memory leaks can be detected during the running of the App. When a memory leak occurs, a reference chain of the leaked object will be generated and the program developer will be notified.

LeakCanary Execution Process

 \bullet Detect retained objects

 \bullet Generate a heap dump file (heap dump)

 \bullet Analyze heap dump files 

 \bullet Classify the leak

LeakCanary Core Principles

LeakCanary automatically detects when Activity and Fragment are destroyed by hooking Android's life cycle, and when they should be garbage collected. These destroyed objects are passed to ObjectWatcher, and ObjectWatcher holds weak references to  them .

LeakCanary detects the type of object

 \bullet Destroyed Activity instance

 \bullet Destroyed Fragment instance

 \bullet Destroyed Fragment View instance

 \bullet Cleared ViewModel instance

ReferenceQueue 与 WeakReference

A weak reference (WeakReference) can be used in conjunction with a reference queue (ReferenceQueue). If the object referenced by the weak reference is reclaimed by the garbage collector, the virtual machine will add the weak reference to the reference queue associated with it. We can use This feature is used to detect whether an object has been successfully reclaimed by the garbage collector

 (In the figure above, first create a weak reference WeakReference, and associate it with a ReferenceQueue. For a weak reference, if you want to refer to it, you need a strong reference obj to refer to this object Object. When we set obj to empty, GC will recycle. When the object referenced by the weak reference is recycled, the weak reference is added to the ReferenceQueue associated with it)

Watchlists and Holdlists in LeakCanary

 The transfer process of weak references

 

Common memory leak cases

1. Singletons lead to memory leaks

Singleton mode is often used in Android development, but if used improperly, it will lead to memory leaks. Because of the static nature of the singleton, its life cycle is as long as the life cycle of the application. If an object is no longer useful, but the singleton still holds its reference, the life cycle of the entire application cannot be recycled normally. Thus causing a memory leak.

public class AppSettings {

    private static volatile AppSettings mInstance;
    private Context context;

    public AppSettings(Context context) {
        this.context = context;
    }

    public static AppSettings getInstance(Context context) {
        if (mInstance == null) {
            synchronized (AppSettings.class){
                if (mInstance == null) {
                    mInstance = new AppSettings(context);
                }
            }
        }
        return mInstance;
    }
}

As in the singleton writing method in the above code, there is a possibility of memory leaks. If the context parameter passed in when we call the getInstance(Context context) method is the context of Activity, Service, etc., it will cause a memory leak. Take Activity as an example, when we start an Activity, and call the getInstance(Context context) method to obtain the single instance of AppSettings, pass in Activity.this as the context, so that the single instance mInstance of the AppSettings class holds a reference to the Activity , when When we exit the Activity, the Activity is useless, but because mInstance as a static singleton (existing in the entire life cycle of the application) will continue to hold the reference of this Activity, resulting in this Activity object cannot be recycled and released, which is caused a memory leak.

In order to avoid memory leaks caused by this singleton, we can change the context parameter to the global context:

    private AppSettings(Context context){
        this.context = context.getApplicationContext(); // 获取 Application 的上下文
    }

2. Static variables cause memory leaks

Static variables are stored in the method area, and their life cycle starts from class loading to the end of the entire process. Once a static variable is initialized, the reference it holds is not released until the process ends. For example, in the following situation, in order to avoid repeated creation of info in the Activity, use sInfo as a static variable.

public class InfoActivity extends AppCompatActivity {

    public static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_info);

        sInfo = new Info(this);
    }

    class Info{
        private Context context;

        public Info(Context context){
            this.context = context;
        }
    }
}

Info is a static member of Activity and holds a reference to Activity, but as a static variable, sInfo must have a longer life cycle than Activity. So when the Activity exits, sInfo still refers to the Activity, and the Activity cannot be recycled, which leads to a memory leak.

In Android development, static holding may often cause memory leaks due to inconsistent life cycles. Therefore, when creating new statically held variables, we need to consider the reference relationship between each member and try to Use less statically held variables to avoid memory leaks. Of course, we can also reset the static variable to null at an appropriate time so that it no longer holds a reference, which can also avoid memory leaks.

3. Non-static inner class causes memory leak

Non-static inner classes (including anonymous inner classes) will hold references to outer classes by default . When the life cycle of non-static inner class objects is longer than the life cycle of outer class objects, memory leaks will result. A typical scenario for memory leaks caused by non-static inner classes in Android development is to use Handler (an anonymous inner class) . Many developers write like this when using Handler:

public class InfoActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_info);

        start();
    }

    private void start(){
        Message message = Message.obtain();
        message.what = 1;
        mHandler .sendMessage(message);
    }
    
    //TODO 存在内存泄漏
    private final Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                //功能代码
            }
        }
    };
}

Some people may say that mHandler does not hold a reference to Activity as a static variable, and its life cycle may not be longer than Activity, so it should not necessarily cause memory leaks. Obviously not the case. Those who are familiar with the Handler message mechanism know that mHandler will be saved as a member variable in the sent message msg, that is, msg holds a reference to mHandler , and mHandler is a non-static inner class instance of Activity, that is, mHandler holds Activity , then we can understand that msg indirectly holds a reference to Activity . After the msg is sent, it is put into the message queue MessageQueue first, and then waits until the polling process of the Looper (MessageQueue and Looper are both related to threads, MessageQueue is a member variable referenced by Looper, and Looper is stored in ThreadLocal). Then when the Activity exits, the msg may still exist in the message queue MessageQueue unprocessed or being processed, then this will cause the Activity not to be recycled, and even the memory leak of the sending Activity.

Usually in Android development, if you want to use internal classes but avoid memory leaks, you generally use static internal classes + weak references .

    MyHandler mHandler;

    public static class MyHandler extends Handler{
        private WeakReference<Activity> mActivityWeakReference;

        public MyHandler(Activity activity){
            // TODO 持有 Activity 的弱引用
            mActivityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            // 处理逻辑
        }
    }

mHandler holds the Activity through a weak reference. When the GC performs garbage collection, it will recycle and release the occupied memory unit when encountering the Activity . This way no memory leaks will occur. The above approach does avoid the memory leak caused by the Activity. The sent msg no longer holds a reference to the Activity, but the msg may still exist in the message queue MessageQueue, so it is better to use the mHandler callback and the mHandler when the Activity is destroyed . The sent message is removed .

 @Override
    protected void onDestroy() {
        super.onDestroy();
        // TODO 把所有的回调都移除
        mHandler.removeCallbacksAndMessages(null);
    }

Another situation where non-static inner classes cause memory leaks is to use Thread or AsyncTask. To avoid memory leaks, you still need to use static inner classes + weak references like the Handler above. 

4. Failure to unregister or callback causes a memory leak

For example, if we register a broadcast in the Activity, if the registration is not canceled after the Activity is destroyed, then the broadcast will always exist in the system, holding the Activity reference like the static inner class mentioned above, resulting in a memory leak. Therefore, after registering the broadcast, you must cancel the registration after the Activity is destroyed. When registering the observer mode, if it is not canceled in time, it will also cause a memory leak. For example, using Retrofit + RxJava to register observer callbacks for network requests also holds external references as anonymous inner classes, so you need to remember to unregister when not in use or destroyed.

5. Timer and TimerTask cause memory leak

Timer and TimerTask are usually used to do some timing or cycle tasks in Android, such as ViewPager for infinite carousel:

private void stopTimer(){
        if (mTimer != null) {
            mTimer.cancel();
            mTimer.purge();
            mTimer = null;
        }
        if (mTimerTask != null) {
            mTimerTask.cancel();
            mTimerTask = null;
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopTimer();
    }

When our Activity is destroyed, it is possible that the Timer is still waiting to execute the TimerTask, and the reference it holds to the Activity cannot be recycled. Therefore, when our Activity is destroyed, we must cancel the Timer and TimerTask immediately to avoid memory leaks.

6. The objects in the collection are not cleaned up, causing memory leaks

This is easier to understand. If an object is put into a collection such as ArrayList, HashMap, etc., this collection will hold a reference to the object. When we no longer need this object, it is not removed from the collection, so as long as the collection is still in use (and this object is useless), this object has caused a memory leak. And if the collection is statically referenced, those useless objects in the collection will cause memory leaks. Therefore, when using a collection, remove unused objects from the collection or clear the collection in time to avoid memory leaks.

7. Resources are not closed or released causing memory leaks

When using IO, File stream or Sqlite, Cursor and other resources, it should be closed in time. These resources usually use buffers when performing read and write operations. If they are not closed in time, these buffer objects will always be occupied and not released, resulting in memory leaks. Therefore, we close them in time when we do not need to use them, so that the buffer can be released in time, thereby avoiding memory leaks.

8. Property animation causes memory leak

Animation is also a time-consuming task. For example, the property animation (ObjectAnimator) is started in the Activity, but when it is destroyed, the cancel method is not called. Although we cannot see the animation, the animation will continue to play. Animation Refer to the control where it is located, and the control where it is located refers to the Activity, which causes the Activity to not be released normally. Therefore, it is also necessary to cancel the attribute animation when the Activity is destroyed to avoid memory leaks.

9. WebView causes memory leak

Regarding the memory leak of WebView, because WebView will occupy memory for a long time after loading a web page and cannot be released, so we need to call its destroy() method to destroy it to release memory after the Activity is destroyed. In addition, I saw this situation when consulting the relevant information about WebView memory leaks: the Callback under the WebView holds the Activity reference, causing the WebView memory to fail to be released, even calling Webview.destory() and other methods cannot solve the problem (after Android5.1 ). The final solution is: before destroying the WebView, you need to remove the WebView from the parent container, and then destroy the WebView .

Guess you like

Origin blog.csdn.net/qq_44950283/article/details/130105942