preface

Hi, everybody. An “easy” question for you:

What is the cause of the Handler memory leak?

What would you say?

That’s the wrong answer

Some friends see this problem and say, is that it? It’s too easy.

“The inner class holds a reference to the outer class, so the Hanlder holds a reference to the Activity and therefore cannot be recycled.”

That’s the wrong answer, or the wrong answer.

A memory leak

An algorithm for reacheability analysis is used in the Java virtual machine to determine whether an object can be reclaimed. The GCRoot object is used as the starting point to search down the path (reference chain). If an object or object group is found to be unreachable, it is reclaimed.

A memory leak is a situation in which some objects (short-period objects) are no longer used, but are referenced by other useful classes (long-period objects), so that useless objects occupy the memory space, resulting in a memory leak.

Therefore, if the above question only answers that the inner class holds a reference to the outer class, and does not indicate by whom the inner class is referenced, then there is no memory leak, because the inner class and the outer class are useless objects and can be recycled normally.

So the key question here is, who is referencing the inner class? Who is referring to Handler?

Let’s do some practical research

Handler Memory leakage occurred. Procedure

1. Send delayed messages

In the first case, a delay message is sent via handler:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler)

        btn.setOnClickListener {
        	// Jump to HandlerActivity
            startActivity(Intent(this, HandlerActivity::class.java))
        }
    }
}

class HandlerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler2)

        // Send a delay message
        mHandler.sendEmptyMessageDelayed(0.20000)

        btn2.setOnClickListener {
            finish()
        }
    }

    val mHandler = object : Handler() {
        override fun handleMessage(msg: Message?). {
            super.handleMessage(msg)
            btn2.setText("2222")}}}Copy the code

In the HandlerActivity, we send a message with a delay of 20 seconds. Then start the HandlerActivity and finish immediately. Let’s see if there’s a memory leak.

Look for memory leaks and analyze

It’s pretty easy to check for memory leaks now. The Heap Dump files are analyzed in AndroidStudio and the memory leaks are clearly marked.

When we run the project, click on Profiler — Memory to see the following image, a real-time picture of running Memory:

You can see that there are two buttons in the image that I have marked:

  • Capture heap dump file buttonThis file will show the usage of the Java heap. After clicking this button, AndroidStudio will generate the heap dump file for us and analyze it.
  • The GC buttonBefore we capture the heap dump file, we usually click GC to recycle some weak references to prevent interference with our analysis.

So we start the HandlerActivity, finish immediately, click the GC button, and then click the Capture heap dump button. AndroidStudio automatically jumps to the following screen:

You can see the Leaks in the top left corner, which is where you’re leaking memory, and you can click to see the leaky class. The lower right corner is the reference path of the memory leak class.

As you can see from this diagram, our HandlerActivity has a memory leak. From the reference path, the reference to the anonymous inner class mHandler is held by Message, and the reference to the Handler is held by MessageQueue…

The complete chain of references for this memory leak should be:

Main thread – > ThreadLocal – > Looper – > MessageQueue – > Message – > Handler – > Activity

The main thread is not reclaimed by the JVM. Running threads are not reclaimed by the JVM, just like static variables.

Handler memory leak Handler memory leak Handler memory leak

2. The child thread did not finish running

The second example, the one we use most often, works in a child thread, such as a network request, and then a UI update via Handler when the request is successful.

class HandlerActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?). {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_handler2)

        // Running child thread
        thread {
            Thread.sleep(20000)
            mHandler.sendEmptyMessage(0)
        }

        btn2.setOnClickListener {
            finish()
        }
    }

    val mHandler = object : Handler() {
        override fun handleMessage(msg: Message?). {
            super.handleMessage(msg)
            btn2.setText("2222")}}}Copy the code

Also run to see the memory leak:

The child thread is an anonymous inner class that holds references to the external class. The child thread itself is always running. As mentioned earlier, the running thread is not recycled, so the reference chain of the memory leak should be:

Running child thread – > Activity

Of course, the Handler also holds a reference to the Activity, but the main cause of the memory leak is the child thread itself. Instead of using the Handler in the operator thread, other variables or methods that call the Activity will still leak.

If we fix the child thread’s memory leak, for example, by stopping the child thread while the Activity is being destroyed, then the Activity can be recycled normally and there is no Handler problem.

Extension question 1: Why does an inner class hold a reference to an outer class

This is because the inner class is written in the same file as the outer class, but is compiled to generate a different class file. The constructor of the inner class passes in an instance of the outer class, and the members of the outer class can then be accessed through this$0.

An inner class can call methods, variables, etc., so it must hold references to the outer class.

To get a better idea of the inner class, post a section of the inner class code using JD-GUI after compiling:


/ / the original code
class InnerClassOutClass{

    class InnerUser {
       private int age = 20; }}/ / class code
class InnerClassOutClass$InnerUser {
    private int age;
    InnerClassOutClass$InnerUser(InnerClassOutClass var1) {
        this.this$0 = var1;
        this.age = 20; }}Copy the code

Extension question 2: How are inner classes in Kotlin different from Java

In fact, you can see that in all of the above code, I added a sentence

btn2.setText("2222")
Copy the code

This is because in Kotlin anonymous inner classes fall into two categories:

  • In the KotlinAn anonymous inner class does not hold an object reference of an external class if it does not use an object reference of an external classStatic anonymous inner classes, and there will be no memory leak.
  • In the KotlinAnonymous inner class if you use a reference to an external class like I just didbtn2At this point, references to external classes will be held and will need to be consideredA memory leakThe problem.

So I purposely added this sentence to make the anonymous inner class hold a reference to the outer class, repeating the memory leak problem.

There is also a difference between Kotlin and Java for inner classes:

  • All inner classes in Kotlin are static by default, so they areStatic inner class.
  • If you need to call external object methods, you doinnerModify to an internal class like Java, and hold references to external classes, and need to consider memory leaks.

Resolving memory leaks

Having said all that, what can you do to fix memory leaks? In fact, all the solutions to memory leaks are similar, and there are mainly the following:

  • Don’t letLong-life objectsholdShort life cycle object“, but withLong-life objectsholdLong-life objectsThe reference.

For example, singleton patterns do not pass in the Activity context.

(There is a big bug written here before. Actually, there is no such thing as passing Application to Glide, because it has been handled by us inside Glide. Instead, use the current component lifecycle and use less Application, or you won’t be able to properly release Glide related resources when a component (such as an Activity) is destroyed.

  • Change the object’s strong reference toA weak reference

A strong reference is an object that is strongly referenced and cannot be reclaimed in any way. Weak references are garbage collected if the object is only associated with weak references (there are no strong references associated with it). A soft reference is a callback to the system when it runs out of memory. Virtual references are objects that have no impact on their lifetime and cannot be used to obtain object instances.

So we change the object to a weak reference to ensure that it is properly collected when garbage is collected, such as when a weak reference instance of an Activity is passed to Handler:

    MyHandler(WeakReference(this)).sendEmptyMessageDelayed(0.20000)

    Inner classes in Kotlin default to static inner classes
    class MyHandler(var mActivity: WeakReference<HandlerActivity>):Handler(){
        override fun handleMessage(msg: Message?). {
            super.handleMessage(msg)
            mActivity.get()? .changeBtn() } }Copy the code
  • An inner class can be written as a static class or an outer class

As in the Hanlder case above, sometimes inner classes are misused and prone to memory leaks. The solution is to write outer classes or static inner classes.

  • Remove potential memory leaks at the end of short cycles

Memory leaks caused by Handler delays, resources not being closed, collections not being cleaned, etc., can be eliminated when the Activity is closed:

@Override
protected void onDestroy(a) {// Remove all messages from handler
  if(mHanlder ! =null){
		mHandler.removeCallbacksAndMessages(null)}super.onDestroy();
}
Copy the code

conclusion

What is the cause of the Handler memory leak?

A memory leak caused by a Handler usually occurs when sending a delayed message. After the Activity is closed, the MessageQueue on the main thread holds a reference to the message, which holds a reference to the Handler. Handler holds a reference to the Activity as an anonymous inner class, so you have the following reference chain.

Main thread – > ThreadLocal – > Looper – > MessageQueue – > Message – > Handler – > Activity

The root cause is that the head of the reference chain, the main thread, will not be recycled, causing the Activity to fail to be recycled and causing a memory leak, with the Handler being the trigger.

The reason why the child thread updates the UI through the Handler is because the running child thread is not recycled, and the child thread holds a reference to the Actiivty (otherwise it would not be able to call the Activity’s Handler), thus causing a memory leak, but the main reason for this is the child thread itself.

So in both cases, the Handler is not the culprit in the event of a memory leak, but the culprit is the thread that leads them.

reference

Handler Memory leak reference describes Kotlin memory leak

Bye bye

Thank you for your reading. If you study with me, you can pay attention to my public account — code on blocks ❤️❤️. Here is a group of Android friends, welcome to join us