47-Memory Leak-Why do I need to call remove() every time I run out of ThreadLocal?

First of all, we need to know that this matter is related to memory leaks, so let us first look at what is a memory leak.

What is a memory leak

Memory leak refers to that when an object is no longer useful, the occupied memory cannot be reclaimed, which is called a memory leak.

Because under normal circumstances, if an object is no longer useful, then our garbage collector GC should clean up this part of the memory. In this case, this part of the memory can be subsequently reallocated to other places for use; otherwise, if the object is useless but cannot be recycled, if such garbage objects accumulate more and more, it will lead to us usable The memory is getting less and less, and finally an OOM error of insufficient memory occurs.

Let's analyze how such a memory leak occurs in ThreadLocal.

Leak of Key In the
previous 1 hour, we analyzed the internal structure of ThreadLocal and learned that every Thread has a variable of type ThreadLocal.ThreadLocalMap, the name of which is called threadLocals. After the thread accesses the ThreadLocal, it will maintain the mapping between the ThreadLocal variable and the specific instance in the Entry in its ThreadLocalMap.

We may perform the ThreadLocal instance = null operation in the business code and want to clean up this ThreadLocal instance, but suppose we strongly reference the ThreadLocal instance in the Entry of the ThreadLocalMap. Then, although the ThreadLocal instance is set to null in the business code, But this reference chain still exists in the Thread class.

The GC will perform reachability analysis during garbage collection. It will find that the ThreadLocal object is still reachable, so the ThreadLocal object will not be garbage collected. This will cause a memory leak.

JDK developers took this into consideration, so Entry in ThreadLocalMap inherits WeakReference weak reference, the code is as follows:

static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
    
    
        super(k);
        value = v;
    }
}

As you can see, this Entry is extends WeakReference. The characteristic of weak references is that if the object is only associated with weak references without any strong references, then the object can be recycled, so weak references will not prevent GC. Therefore, this weak reference mechanism avoids the memory leak problem of ThreadLocal.

This is why the entry key uses weak references.
Leakage of Value
However, if we continue to study, we will find that although each Entry of ThreadLocalMap is a weak reference to the key, this Entry contains a strong reference to the value, or the code just now:

static class Entry extends WeakReference<ThreadLocal<?>> {
    
    
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
    
    
        super(k);
        value = v;
    }
}

As you can see, the line of code value = v represents the occurrence of a strong reference.

Under normal circumstances, when the thread terminates, the value corresponding to the key can be garbage collected normally because there is no strong reference. But sometimes the life cycle of a thread is very long. If the thread does not terminate late, then the ThreadLocal and its corresponding value may no longer be useful. In this case, we should ensure that they can all be recycled normally.

In order to better analyze this problem, we use the following picture to look at the specific reference link (solid line represents strong reference, dashed line represents weak reference): As
Insert picture description here
you can see, the left side is the reference stack, and there is a ThreadLocal in the stack. A reference to a thread and a reference to a thread, on the right is our heap, and in the heap is an instance of an object.

Let's focus on the following link: Thread Ref → Current Thread → ThreadLocalMap → Entry → Value → value instance that may be leaked.

This link always exists with the existence of threads. If the thread does not stop performing time-consuming tasks, then when garbage collection performs reachability analysis, this Value is reachable, so it will not be recycled. But at the same time, we may have completed the business logic processing, and this Value is no longer needed. At this time, a memory leak problem has occurred.

JDK also considers this problem. When executing ThreadLocal's set, remove, rehash and other methods, it will scan the Entry whose key is null. If it finds that the key of an Entry is null, it means that its corresponding value has no effect. So it will set the corresponding value to null, so that the value object can be recycled normally.

But assuming that ThreadLocal is no longer used, then the set, remove, and rehash methods will not actually be called. At the same time, if the thread has been alive and does not terminate, then the call chain just now will always exist. This leads to a memory leak of value.

How to avoid memory leaks

After analyzing this problem, how to solve it? The solution is the title of our class: call the remove method of ThreadLocal. Calling this method can delete the corresponding value object, which can avoid memory leaks.

Let's take a look at the source code of the remove method:

public void remove() {
    
    
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

It can be seen that it first obtained the reference to ThreadLocalMap and called its remove method. The remove method here can clean up the value corresponding to the key, so that the value can be recycled by the GC.

Therefore, after using ThreadLocal, we should manually call its remove method, the purpose is to prevent the occurrence of memory leaks.

Guess you like

Origin blog.csdn.net/Rinvay_Cui/article/details/111056482