First, basic knowledge

1.1 Memory Leak and overflow:

  • A Memory Leak refers to a waste of Memory space caused by an unwanted object continuously occupying the Memory or the Memory of the unwanted object is not released in a timely manner

For example, when the Activity’s onDestroy() method is called, the Activity itself and its associated views, bitmaps, and so on should be reclaimed. However, if a background thread holds a reference to the Activity, the memory occupied by the Activity cannot be reclaimed, resulting in OOM and eventually Crash.

  • Out Of Memory means that an application does not have enough Memory space to use when it requests Memory

Similarities: Application running problems, performance degradation, or crash. Difference:

  1. Memory leakage is one of the causes of memory overflow. If memory leakage is severe, memory overflow will occur
  2. Memory leaks are caused by software design flaws and can be avoided by improving the code; Memory overflows can be reduced by adjusting the configuration, but cannot be completely avoided

1.2 Java memory allocation:

  1. Static storage: Exists for the entire duration of a program and is allocated at compile time to store static data and constants
  2. Stack area: When a method is executed, local variables inside the method body are created in the stack area memory, which is automatically freed when the method ends

Heap area: Usually holds new objects that are collected by the Java garbage collector

1.3 Four types of reference:

  1. Strong references: the Jvm would rather throw an OOM than let the GC reclaim an object with a StrongReference
  2. SoftReference: an object that is reclaimed only when the memory space is insufficient
  3. WeakReference: during GC, once an object with only weak references is found, its memory will be reclaimed regardless of whether the current memory space is sufficient or not
  4. PhantomReference: can be collected by the GC at any time. When the garbage collector is about to reclaim an object and finds that it has a PhantomReference, it adds the PhantomReference to its associated reference queue before reclaiming the object’s memory. A program can determine whether an object is about to be reclaimed by determining whether a virtual reference to the object exists in the reference queue. Can be used as a flag for GC to reclaim an Object.

A memory leak is when a new Object (strong reference) cannot be collected by the GC

1.4 non-static inner Classes and Anonymous Classes

Non-static inner classes and anonymous classes implicitly hold a reference to an external class

1.5. Static inner class:

An external class shares the same static inner class no matter how many instances it has, so the static inner class does not hold references to the external class

2. Analysis of memory leakage

2.1. Resources are not closed

Cursors, InputStream/OutputStream, and File are all cursors with buffers, so close them when they are no longer needed to recycle memory. They are buffered both inside and outside the Java Virtual machine, and simply setting references to NULL without closing them tends to cause memory leaks. Also, unregister resources that need to be registered, such as BroadcastReceiver. The animation also stops when the interface is no longer visible to the user.

2.2, the Handler

In the following code

public class HandlerActivity extends AppCompatActivity { private Handler handler = new Handler() { @Override public void  handleMessage(Message msg) { super.handleMessage(msg); }}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); }}Copy the code

After the Handler object is declared, the IDE gives the developer a prompt:

This Handler class should be static or leaks might occur.
Copy the code

Handler needs to be declared static, otherwise memory leaks may occur

When the application is first started, the system creates a Looper object in the main thread. Looper implements a simple Message queue, which is used to loop through messages. All major application-level events (such as Activity lifecycle method callbacks, Button clicks, and so on) are contained in the Message, which is added to the Looper, which then loops through the Message. The Looper for the main thread exists throughout the application lifecycle. When the main thread creates a Handler object, it is bound to a Looepr object, and the Message distributed to the Message queue holds a reference to Handler so that the system can call Handle’s handlerMessage(Message) method when Looper processes the Message. In the code above, the Handler is not a static inner class, so it holds a reference to the outer class (HandlerActivity). When there are delayed tasks in the Handler or the queue of tasks waiting to be executed is too long, because the message holds a reference to the Handler and the Handler holds potential references to its external classes, the reference relationship will remain until the message is processed. This causes HandlerActivity to fail to be collected by the garbage collector, resulting in a memory leak.

For example, in the following code, in the onCreate() method, tell the handler to print the Log every second

public class HandlerActivity extends AppCompatActivity { private final String TAG = "MainActivity"; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); handler.postDelayed(new Runnable() { @Override public void run() { Log.e(TAG, "Hi"); handler.postDelayed(this, 1000); }}, 6000); }}Copy the code

Looking at Handler’s source code, we can see that the postDelayed method is essentially sending a delayed Message

	public final boolean postDelayed(Runnable r, long delayMillis){
		return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
Copy the code

The first thing to realize is that both non-static classes and anonymous inner classes hold implicit references to external classes. When the HandlerActivity life cycle ends, the delayed Message holds a reference to the Handler, and the Handler holds an implicit reference to the external class (HandlerActivity). The reference continues until the Message is processed, and there is no conditional statement that can terminate the Handler, preventing the HandlerActivity from reclaiming and eventually causing a memory leak.

LeakCanary is used here to detect memory leaks (this tool is described below). Start HandlerActivity and exit. After three or four seconds, we can see that LeakCanary indicates that the application has a memory leak

As you can see from the text prompt, the problem is with Handler

The solution is to remove all callbacks and messages from the Handler after the HandlerActivity exits

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

2.3, the Thread

When a child thread is started to perform a time-consuming operation, if a configuration change (such as a vertical screen switch) causes the Activity to be recreated, the old Activity is typically handed over to the GC for collection. However, if the thread created is declared as a non-static inner class or an anonymous class, the thread retains an implicit reference to the old Activity. The thread is not destroyed when the run() method of the thread has not finished executing, so the old referenced Activity is not destroyed, and all resource files associated with the Activity are not recycled, causing a serious memory leak.

So in summary, there are two main reasons for memory leaks from threads:

  1. Uncontrolled thread life cycle. Threads and asyncTasks in an Activity are not destroyed when the Activity is destroyed. Threads wait until the run() execution ends, as is the case with AsyncTask doInBackground() methods
  2. Non-static inner classes and anonymous classes implicitly hold a reference to an outer class

For example, start a thread in the onCreate() method and use a static threadIndex variable to mark the number of threads being created

public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (true) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); }}Copy the code

Rotate the screen a few times to see the output:

04-04 08:15:16. 373, 23731-23911 / com. Czy. Leakdemo E/ThreadActivity: Hi--2 04-04 08:15:16.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:16.374 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:16.374 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:16.852 23731-26202/com.czy. Leakdemo E/ThreadActivity: Hi--5 04-04 08:15:18.374 23731-23911/com.czy.leakdemo E/ThreadActivity: Hi--2 04-04 08:15:18.374 23731-26132/com.czy.leakdemo E/ThreadActivity: Hi--4 04-04 08:15:18.376 23731-23970/com.czy.leakdemo E/ThreadActivity: Hi--3 04-04 08:15:18.376 23731-23820/com.czy.leakdemo E/ThreadActivity: Hi--1 04-04 08:15:18.852 23731-26202/ com.czY. leakDemo E/ThreadActivity: Hi--5...Copy the code

Even when a new Activity is created, the threads created in the old Activity continue to execute, causing serious memory leaks as the Activity is unable to free up memory

LeakCanary test results:

To avoid memory leaks caused by threads, you can actively stop a Thread after the Activity exits. For example, you can set a Boolean threadSwitch variable to the Thread to control the start and stop of the Thread

public class ThreadActivity extends AppCompatActivity { private final String TAG = "ThreadActivity"; private int threadIndex; private boolean threadSwitch = true; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new Thread(new Runnable() { @Override public void run() { int j = threadIndex; while (threadSwitch) { Log.e(TAG, "Hi--" + j); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } @Override protected void onDestroy() { super.onDestroy(); threadSwitch = false; }}Copy the code

To keep Thread running, do the following:

  1. Change the Thread to a static inner class to break the Activity’s strong reference to Thread
  2. Use weak references inside threads to save Context references and cut off strong references to activities by threads
public class ThreadActivity extends AppCompatActivity { private static final String TAG = "ThreadActivity"; private static int threadIndex; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_thread); threadIndex++; new MyThread(this).start(); } private static class MyThread extends Thread { private WeakReference<ThreadActivity> activityWeakReference; MyThread(ThreadActivity threadActivity) { activityWeakReference = new WeakReference<>(threadActivity); } @Override public void run() { if (activityWeakReference == null) { return; } if (activityWeakReference.get() ! = null) { int i = threadIndex; while (true) { Log.e(TAG, "Hi--" + i); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }Copy the code

2.4, the Context

In the process of using toasts, if the application pops up multiple toasts in succession, the toasts will overlap. Therefore, the following method can be used to ensure that the current application displays only one Toast at any time, and the text information of the Toast can be updated immediately

*/ public class ToastUtils {private static Toast; public static void showToast(Context context, String info) { if (toast == null) { toast = Toast.makeText(context, info, Toast.LENGTH_SHORT); } toast.setText(info); toast.show(); }}Copy the code

Then, use it in your Activity

public class ToastActivity extends AppCompatActivity { private static int i = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_toast); } public void showToast(View View) {ToastUtils. ShowToast (this, "display Toast:" + (i++)); }}Copy the code

After clicking the Button once to make Toast pop up, exit ToastActivity and LeakCanary will prompt that it has caused a memory leak

Toast.mContext: toast. mContext: toast. mContext: toast. mContext: toast. mContext: toast. mContext: toast. mContext: toast. mContext The Toast variable in ToastUtils is statically typed and has a lifetime as long as the entire application, resulting in ToastActivity not being released. Therefore, a reference to a Context cannot extend beyond its own lifetime.

public Toast(Context context) {
        mContext = context;
        mTN = new TN();
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
        mTN.mGravity = context.getResources().getInteger(
                com.android.internal.R.integer.config_toastDefaultGravity);
    }
Copy the code

The solution is to use ApplicationContext instead, because ApplicationContext exists with the application and is not dependent on the Activity lifecycle

*/ public class ToastUtils {private static Toast; public static void showToast(Context context, String info) { if (toast == null) { toast = Toast.makeText(context.getApplicationContext(), info, Toast.LENGTH_SHORT); } toast.setText(info); toast.show(); }}Copy the code

2.5, collections,

Sometimes we need to add objects to a collection container (such as an ArrayList), and when an object is no longer needed, the GC will not be able to reclaim the object if the reference to the object is not removed from the collection. If the collection is static, the memory leak is even worse. Therefore, when an object is no longer needed, it needs to be actively removed from the collection

Third, LeakCanary

LeakCanary is an open source library developed by Square to detect memory leaks. You can easily detect memory leaks in the Debug package

To import the LeakCanary library, just add it in your project’s build.gradle file

TestCompile 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.5' debugCompile 'com. Squareup. Leakcanary: leakcanary - android: 1.5' releaseCompile 'com. Squareup. Leakcanary: leakcanary - android - no - op: 1.5'Copy the code

Gradle’s powerful configurability ensures that memory leaks are checked only when a debug version is compiled, while checks are automatically skipped when a release version is compiled to avoid performance impact

If you just want to detect memory leaks in your Activity, do the following initialization in your custom Application

/** * Author: Ye Ying Shi Ye * Time: 2017/4/4 12:41 * Description:  */ public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); LeakCanary.install(this); }}Copy the code

If you also want to monitor Fragmnet for memory leaks, do the following initialization in your custom Application

/** * Author: Ye Ying Shi Ye * Time: 2017/4/4 12:41 * Description:  */ public class MyApplication extends Application { private RefWatcher refWatcher; @Override public void onCreate() { super.onCreate(); refWatcher = LeakCanary.install(this); } public static RefWatcher getRefWatcher(Context context) { MyApplication application = (MyApplication) context.getApplicationContext(); return application.refWatcher; }}Copy the code

Then create a listener on onDestroy() in the Fragment you want to monitor

public class BaseFragment extends Fragment { @Override public void onDestroy() { super.onDestroy(); RefWatcher refWatcher = MyApplication.getRefWatcher(); refWatcher.watch(this); }}Copy the code

When a memory leak occurs during testing the Debug version, LeakCanary will automatically display a notification bar showing the detection results