The interviewer will ask you ThreadLocal again, and you will go back like this!

hi, Hello, I'm Lao Tian

We have to master ThreadLocal whether for work or interview. Here's a ThreadLocal four questions:

  • What is the principle of ThreadLocal?
  • How can ThreadLocal cause a memory leak?
  • How can weak references become the back pot man of OOM?
  • What is the best way to use ThreadLocal?

If we can handle the above four consecutive questions, we can easily deal with jobs and interviews.

Let's start with the code case. It's all hooligans without code!

Code case

1. First look at the code: simulates a thread with the number of threads_ LOOP_ In the thread pool of size, all threads share a ThreadLocal variable. When each thread executes, a large List set is inserted. Since 500 cycles are executed, that is, 500 threads are generated, and each thread will attach to a ThreadLocal variable:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalOOMDemo {

    private static final int THREAD_LOOP_SIZE = 500;
    private static final int MOCK_BIG_DATA_LOOP_SIZE = 10000;

    private static ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_LOOP_SIZE);

        for (int i = 0; i < THREAD_LOOP_SIZE; i++) {
            executorService.execute(() -> {
                threadLocal.set(new ThreadLocalOOMDemo().addBigList());
                Thread t = Thread.currentThread();
                System.out.println(Thread.currentThread().getName());
                
            });
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }

    private List<User> addBigList() {
        List<User> params = new ArrayList<>(MOCK_BIG_DATA_LOOP_SIZE);
        for (int i = 0; i < MOCK_BIG_DATA_LOOP_SIZE; i++) {
            params.add(new User("xuliugen", "password" + i, "male", i));
        }
        return params;
    }

    class User {
        private String userName;
        private String password;
        private String sex;
        private int age;

        public User(String userName, String password, String sex, int age) {
            this.userName = userName;
            this.password = password;
            this.sex = sex;
            this.age = age;
        }
    }
}

2. Set the JVM parameters and set the maximum memory to 256M to simulate OOM:

Parameter setting

3. Run the code and output the result:

OOM log

It can be seen that an error is reported when the single thread pool is executed to 212, resulting in an OOM memory overflow error.

4,While running the code, open the JDK tool jconsole to monitor memory changes (you can directly enter jconsole in cmd to call enter):

Memory change

It can be seen that the above memory is incremented to the maximum value set by the JVM (roughly), then an exception is thrown and the program exits!

5,This example can be a good demonstration: after each thread in the thread pool uses the ThreadLocal object, it will no longer be used. Because the threads in the thread pool will not exit, the threads in the thread pool will exist, and the ThreadLocal variable will also exist, occupying memory! Cause OOM overflow!

Why does ThreadLocal leak memory

It is said that ThreadLocal is improperly used. The reasons for OOM caused by ThreadLocal are described in detail below:

1. First, let's take a look at the schematic diagram of ThreadLocal:

Relationship among Thread, ThreadLocal, ThreadLocalMap and Entry:

relationship

The above figure describes that there is only one ThreadLocalMap in a Thread, and there can be multiple ThreadLocal objects in a ThreadLocalMap. One ThreadLocal object corresponds to the Entry of one of the threadlocalmaps (that is, a Thread can be attached to multiple ThreadLocal objects).

These references exist throughout the lifetime of ThreadLocal.

Look at the figure below: solid lines represent strong references and dotted lines represent weak references.

2. The implementation of ThreadLocal is as follows: each Thread maintains a ThreadLocalMap mapping table. The key of this mapping table is the ThreadLocal instance itself, and the value is the Object that really needs to be stored.

3. In other words, ThreadLocal itself does not store values, it is just used as a key to let the thread obtain values from ThreadLocalMap. It is worth noting that the dotted line in the figure indicates that ThreadLocalMap uses the weak reference of ThreadLocal as the key, and the weakly referenced objects will be recycled during GC.

4. ThreadLocalMap uses the weak reference of ThreadLocal as the key. If a ThreadLocal has no external strong reference to reference it, the ThreadLocal is bound to be recycled during system GC. In this way, entries with null key will appear in ThreadLocalMap, and there is no way to access the values of these entries with null key, If the current thread does not end for a long time, the values of these null key entries will always have a strong reference chain: thread ref - > thread - > threalocal map - > Entry - > value can never be recycled, resulting in memory leakage.

5. Generally speaking, a Map with weak references is used in ThreadLocal. The type of Map is threadlocal.threadlocalmap. The key in the Map is a ThreadLocal instance. This Map does use weak references, but weak references are only for keys. Each key has a weak reference to ThreadLocal. When the ThreadLocal instance is set to null, there is no strong reference to the ThreadLocal instance, so ThreadLocal will be recycled by gc.

However, our value cannot be recycled, and this value will never be accessed, so there is a memory leak. Because there is a strong reference connected from current thread. Only after the current thread ends, the current thread will not exist in the stack, the strong reference will be broken, and the current thread and Map value will be recycled by GC. The best way is to call the remove method of threadlocal, which will be discussed later.

6. In fact, this situation has been taken into account in the design of ThreadLocalMap, and some protective measures have been added: when get(),set(),remove() of ThreadLocal, all value s with null key s in ThreadLocalMap will be cleared. This point was also mentioned in the previous section!

7. However, these passive precautions do not guarantee that memory leaks will not occur:

(1)use static of ThreadLocal´╝îExtended ThreadLocal Life cycle, which may lead to memory leakage.
(2)Allocation used ThreadLocal No more calls get(),set(),remove()Method, it will lead to memory leakage, because this memory always exists.

Is OOM a weak reference pot?

On the surface, the root cause of the memory leak is the use of weak references. Online articles mostly focus on analyzing that ThreadLocal uses weak references, which will lead to memory leakage, but another problem is also worth thinking about: why use weak references instead of strong references?

For JVM, see: 20000 words! Summary of JVM core knowledge and 18 serial guns as a gift

Let's take a look at the official documents:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. To cope with very large and long-term uses, hash tables use weakly referenced key s.

Let's discuss it in two cases:

(1) key uses strong reference: the referenced ThreadLocal object is recycled, but ThreadLocalMap still holds the strong reference of ThreadLocal. If it is not deleted manually, ThreadLocal will not be recycled, resulting in Entry memory leakage.

(2) key uses weak reference: the referenced ThreadLocal object is recycled. Since ThreadLocalMap holds a weak reference to ThreadLocal, ThreadLocal will be recycled even if it is not deleted manually. value will be cleared the next time ThreadLocalMap calls set, get and remove.

Comparing the two cases, we can find that the life cycle of ThreadLocalMap is as long as that of Thread. If the corresponding key is not manually deleted, it will lead to memory leakage, but the use of weak reference can provide one more layer of guarantee: the weak reference ThreadLocal will not leak memory, and the corresponding value will be cleared the next time ThreadLocalMap calls set, get and remove.

Therefore, the root cause of ThreadLocal memory leak is: because the life cycle of ThreadLocalMap is as long as Thread, if the corresponding key is not manually deleted, it will lead to memory leak, not weak reference.

ThreadLocal best practices

1. Based on the above analysis, we can understand the causes and consequences of ThreadLocal memory leakage, so how to avoid memory leakage?

The answer is: every time ThreadLocal is used, its remove() method is called to clear the data.

About thread pools: Quick fix thread pool

When the thread pool is used, the ThreadLocal is not cleared in time, which is not only a memory leak problem, but also a business logic problem. Therefore, using ThreadLocal is the same as unlocking after locking, and cleaning after use.

be careful:

Not all places where ThreadLocal is used are removed () at the end. Their life cycle may be as long as the life cycle of the project, so make appropriate choices to avoid business logic errors! But the first thing to ensure is that the size of the data saved in ThreadLocal is not very large!

2. Then we modify the initial code as follows:

Uncomment: threadLocal.remove(); As a result, there will be no OOM. It can be seen that the change of heap memory is jagged, which proves that the memory of ThreadLocal is released after each remove()! The number of threads in the thread pool continues to increase!

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadLocalOOMDemo {

    private static final int THREAD_LOOP_SIZE = 500;
    private static final int MOCK_BIG_DATA_LOOP_SIZE = 10000;

    private static ThreadLocal<List<User>> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {

        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_LOOP_SIZE);

        for (int i = 0; i < THREAD_LOOP_SIZE; i++) {
            executorService.execute(() -> {
                threadLocal.set(new ThreadLocalOOMDemo().addBigList());
                Thread t = Thread.currentThread();
                System.out.println(Thread.currentThread().getName());
                threadLocal.remove(); 
            });
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executorService.shutdown();
    }

    private List<User> addBigList() {
        List<User> params = new ArrayList<>(MOCK_BIG_DATA_LOOP_SIZE);
        for (int i = 0; i < MOCK_BIG_DATA_LOOP_SIZE; i++) {
            params.add(new User("xuliugen", "password" + i, "male", i));
        }
        return params;
    }

    class User {
        private String userName;
        private String password;
        private String sex;
        private int age;

        public User(String userName, String password, String sex, int age) {
            this.userName = userName;
            this.password = password;
            this.sex = sex;
            this.age = age;
        }
    }
}

Uncomment: threadLocal.remove(); As a result, there will be no OOM. It can be seen that the change of heap memory is jagged, which proves that the memory of ThreadLocal is released after each remove()! The number of threads in the thread pool continues to increase!

Well, that's all for today's sharing. I look forward to your third consecutive: follow + Watch + forward.

Reference: blog.csdn.net/xlgen157387

Posted on Fri, 26 Nov 2021 00:08:30 -0500 by echo10