ThreadLocal memory leak case analysis and Practice

Use code practice to thoroughly understand the memory leakage of ThreadLocal. Many articles are ambiguous. In the communication with group friends, we basically figured out what ThreadLocal is. Most articles can't combine knowledge points with practical use.

Write a small example first

/**
 * Test threadLocal memory leak
 * 01:Fix 6 threads, and each thread holds a variable
 * Normally, there will be 6 * 5 = 30M memory that cannot be recycled, and the rest will be overwritten in the set method.
 */
public class ThreadLocalOutOfMemoryTest {
    static class LocalVariable {
        //Total 5M
        private byte[] locla = new byte[1024 * 1024 * 5];
    }

    // (1) A thread pool with a core thread count and a maximum thread count of 6 is created, which ensures that there are 6 threads running in the thread pool at any time
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(6, 6, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    // (2) A ThreadLocal variable is created, the generic parameter is LocalVariable, and the interior of LocalVariable is a Long array
    static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();

    public static void main(String[] args) throws InterruptedException {
        // (3) Put 50 tasks into the thread pool
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    // (4) Set value to threadLocal variable
                    LocalVariable localVariable = new LocalVariable();
                    // Will cover
                    ThreadLocalOutOfMemoryTest.localVariable.set(localVariable);
                    // (5) Manually clean up ThreadLocal
                    System.out.println("thread name end: " + Thread.currentThread().getName() + ", value:"+ ThreadLocalOutOfMemoryTest.localVariable.get());
//                    ThreadLocalOutOfMemoryTest.localVariable.remove();

                }
            });

            Thread.sleep(1000);
        }

        // (6) It doesn't matter whether the key is disabled or not. As long as the thread held exists, it cannot be recycled.
        //ThreadLocalOutOfMemoryTest.localVariable = null;
        System.out.println("pool execute over");
    }
}
Copy code
  1. First open jvisualvm.exe under the jdk toolkit and run this small example. Wait for the program to end and actively trigger a garbage collection.

We can see that about 30M of memory has not been recycled. Consistent with our expected conclusion.

a. Why not 50*5=250M?

Because java.lang.ThreadLocal#set() will overwrite, each thread holds a copy, and the same thread executes multiple times. Because the key is the same ThreadLocal variable, it will be routed to the same position of the array and directly overwrite the last value.

b. Why can't 30M of memory be recycled?

Because six threads in the thread pool survive, it holds a strong reference to ThreadLocalMap

  1. Open the code at 5 and run the observation heap memory again:

This is easy to understand because we manually called the remove method to clean up the objects in ThreadLocalMap (pointing the Entry object to null). There is no strong reference. Of course, it is directly recycled.

  1. Close code 5 and then open code 6:

Consistent with the first case, there is still 30M of memory that cannot be recycled. Therefore, the recycling of ThreadLocalMap has little to do with the weak reference of the Key of the Entry object.

As long as the thread holding the ThreadLocalMap survives, the value will not be recycled regardless of whether the Key is invalid or not. That's why we are required to clean up immediately in the process of use.

Overall structure of ThreadLocalMap

  1. ThreadLocalMap is the internal class of ThreadLocal, which internally maintains an Entry type table array.
  2. The Entry Object consists of two member variables: key and Value. Key is a weak reference to ThreadLocal Object ③, and Value is an Object type Object stored for this ThreadLocal.
  3. The key weakly references threadLocal. If the threadLocal object is only weakly referenced by the key of this ThreadLocalMap for each GC, the object will be recycled and the key is null.
  4. The strong reference of the program to the ThreadLocal variable. When the strong reference disappears, ThreadLocal will be recycled. Similar to our code:
ThreadLocal local = new ThreadLocal();
local = null;
Copy code
  1. The Thread object holds a strong reference to the entire ThreadLocalMap.

Question: ③ why use weak references here?

Suppose a scenario where ThreadLocal is used in the Tomcat thread pool. Threads hold strong references to ThreadLocalMap, resulting in a long life cycle of ThreadLocalMap. Suppose that a ThreadLocal object is created for each request to store the session. If it is not destroyed in time, the memory occupied by the whole ThreadLocalMap will become larger and larger. For weak applications that set the key to the ThreadLocal object in advance, when the program does not reference the ThreadLocal object, gc can quickly recycle the ThreadLocal object, and the key of the Entry is null. In the ThreadLocalMap#set method, the Entry object with key=null will be cleaned up to recover memory.

Summary

  • Thread - > ThreadLocalMap is one-to-one. One thread maintains one ThreadLocalMap.
  • Thread - > ThreadLocal is one to many. A thread can have multiple ThreadLocal objects, which are hash mapped to indexes not used in the array.
  • The weak reference of key in Entry can be regarded as a method to help clean up the Entry key value pairs in ThreadLocalMap. However, you need to manually set the threadLocal variable to null: threadLoca=null
  • ThreadLocal memory leakage has little to do with the weak reference of the key, because no one encodes threadLoca=null in the actual coding, so whether to recycle ThreadLocalMap mainly depends on the life cycle of the thread.
  • In the scenario of long life cycle of the thread, after running out of variables, it is necessary to call ThreadLocalMap#remove() to actively clean up.


Author: Ma Nong, Chengnan
Link: https://juejin.cn/post/6982121384533032991
Source: rare earth Nuggets
The copyright belongs to the author. For commercial reprint, please contact the author for authorization, and for non-commercial reprint, please indicate the source.

Keywords: Java Back-end

Added by piersk on Tue, 16 Nov 2021 11:08:05 +0200