Android memory leaks

Memory leaks have been found occasionally in projects recently. At the beginning or a face meng forced to check to check, has not been a clear thinking. These days, I am going to tidy up and study seriously. I’m going to learn from a “how to actively leak” perspective, and then familiarize myself with the results of different detection methods, so that I can quickly fix the problem in the future.

java gc

The first big premise is the Java GC. In most virtual machines (including Android ART), Java uses the “reachabability analysis” algorithm to reclaim memory. The principle is: There are several references as root nodes, and for any object, if it is iterated from root and no reference chain is found for it, the object is marked as useless and destroyed during GC.

What is a leak

A memory leak is an object that is no longer in use but has not been destroyed because root holds references. It doesn’t matter if it happens once or twice. If this occurs frequently, the available memory will gradually run out, and eventually an OOM occurs when insufficient memory is found during a memory request. It is important to be clear that only strong references will leak, while weak references will have little impact because of their special mechanism.

What would leak

The leak affects large objects, such as resources, Bitmaps, and activities.

How a leak happens

First, let’s look at it from another perspective. How do you actively leak memory? Figure out a way to give him an ever-present strong reference, of course.

static

The static keyword makes a variable a class variable associated only with the class, independent of the instance. Its life cycle is long, from startup to shutdown of the app. So just use a static reference to a large object and leak! Here’s an example:

static Activity activity;
Copy the code

This is the simplest and most crude way to hold a reference to an activity so that the object is not destroyed after the activity exits.

static View view;
Copy the code

When a View is initialized, it uses the context, and we know that when we customize the View, when we override the constructor. So if a View is held like this, that context won’t be released either.

innerClass

An inner class has a feature that holds a reference to an outer class. If an instance of the inner class is always alive, then an instance of the outer class Activity is always alive. For example, holding a static inner class reference:

static LeakInnerClass context;

class LeakInnerClass {
    Context context;
}
Copy the code

Or we used to use asyncTask to create an anonymous inner class to perform asynchronous tasks, which would be exposed if the asynchronous task was still executing after our activity exits.

void leakAsyncTask(a){
    new AsyncTask<Void,Void,Void>(){

        @Override
        protected Void doInBackground(Void... params) {
            while(true) {// blah, blah, blah, blah, blah
            }
            return null; }}; }Copy the code

And start your own anonymous thread:

void leakThread(a){
    new Thread(){
        @Override
        public void run(a) {
            while (true) {// blah, blah, blah, blah, blah
            }
        }
    }.start();
}
Copy the code

Also, when using handlers, if an anonymous handler is used, the handler is hidden in the message queue with a reference to the activity. The message is not processed, causing a memory leak. Similarly, there are timerTask, etc.

register

We usually use a lot of third-party libraries, such as ButterKnife EventBus RxJava and so on. Sometimes we need to get the system service, getSystemService. When used, there is a registerd or bind operation first, and a reference to the activity is passed at creation time. If you do not have unregister or unbind at the end of your activity, you will cause a memory leak.

How to detect leaks

The easiest way, of course, is to use LeakCanary. Just add this tool to your project and you’ll soon be alerted when a leak occurs. See here for specific usage.

In addition, Android Studio’s slash-and-burn approach is also good, so here’s an example of how I use it.

A Leak detection process

The preparatory work

First, I wrote two activities, one MainActivity and one MemoryLeakActivity, and the logic is: MainActivity has a button that will call to MemoryLeakActivity, where a memory leak will deliberately occur, with the following code:

Before we start, familiarize ourselves with this one more time

(Forgive my bad brush)

This monitor will monitor the status of the currently selected APP. For now, just focus on where I marked 123.

First, the Memory is the current Memory usage of the app:

1. Generate a.hprof file for the current Java heap. This file reflects the details of memory in the current Java heap.

2. Perform a gc manually

3. This piece is very important. First, it has two parts, blue and gray. The blue part is the current memory usage, and the gray part is the maximum memory size that the app is limited to. When the blue part gets bigger and bigger and ends up the same as the gray part, it means that we are using too much memory and running out of memory, and a GC is done and the gray part is raised.

The naked eye

Ok, with this tool introduced, let’s get our hands dirty. First open the app, click the button to jump to the activity that will leak, then press the back button, then press the button again… This repeated operation:

At the same time, if you look at the memory window of the monitors, it’s normal to see that the blue portion grows with each new activity that is started. But when you return, the activity is “exited”, but the blue part remains the same. Over and over again, the blue part keeps growing. If you use more memory, you can infer that a memory leak has occurred

Automatic analysis

Let’s take a look at it with Android Studio. After repeating the above a few times, return to MainActivity, then click the Dump Java Heap button, and wait while Android Studio dumps the horof file for us. After success, it will automatically open:

In this screen, we see a bar called Analyzer Tasks on the far right. When we open it, we see two options. We’re looking for a memory leak in our activity, so remove the check √ from the repeated string. Click on the green triangle to the right, and you’ll see the current leaked Activity reference displayed in the Analysis Results column below:

Click on the first item and the Reference Tree at the bottom shows the specific Reference:

Generally, the first one is where we have the spill. In the figure, this$0 means an implicit reference. That is, our activity has a memory leak because of an internal class.

Click the second item in results to see the reference tree below:

We can see that there is an explicit leakCntextRef reference, indicating that we have a reference named leakCntextRef holding the activity. Look back at our code, and sure enough, it checked.

expand

Android Studio’s analysis is relatively simple and small, we can export the hprof and use mat to analyze, see here.

How to fix the leak

If there is a leak, fix it and avoid the problem. So what’s the solution? Simply, the leak is due to holding an activity reference that cannot be destroyed, so there are only two options: cancel the reference as soon as possible, or let the reference wait a little longer but be destroyed as soon as the gc is ready.

According to this idea:

  • We can use contxt as a static variable in our code. We can use contxt as a weak variable in our code.
  • For inner classes, try to use static inner classes so that no external class references are held. If you need an external class reference to do something, manually assign a weak reference.
  • When it comes to anonymous inner classes, don’t try to make them easy. If you can’t, write them as outer classes.
  • For asynchronous operations, try to use something more manageable, such as rxJava, rather than the old AsyncTask. If necessary, it is best to add a termination condition, which should end when you exit the Activity.
  • When using RX, you can obtain the Subscripeion when subscribing (), manually unSubscribe() when not in use, or directly bind() to the Activity life cycle, such as using RxActivity management.
  • When using handler, add remove() to the activity onDestroy().
  • When certain resources are acquired, release them after use
  • When you’re using something big like a Bitmap or something, you recycle it, right
  • Finally, when using various third-party libraries or system services, remember to register or bind and unregister and unbind.