A few days ago, I was asked if ThreadLocal does have memory leaks, so I took the opportunity to talk to you about whether ThreadLocal does have memory leaks and how to avoid them.

The threadLocals property of Thread

It all starts with the threadLocals property of Thread. Let’s take a look at this property:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

The threadLocals property is a static class ThreadLocalMap in ThreadLocal, which is a map and is maintained and managed by ThreadLocal.

So what does this threadLocals, which is this map, store?

Let’s look at ThreadLocalMap:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}
Copy the code

A ThreadLocalMap is a custom hash map that stores ThreadLocal objects and values as key-value pairs within an Entry.

The relationship between the threadLocals and ThreadLocal properties of a Thread looks like this:

threadLocals.png

Note that the Entry key, or ThreadLocal object, exists as a weak reference. This will be one of the highlights of this article’s memory leak analysis, but let’s move on to the relationship between threads and ThreadLocal.

Relationship between Thread and ThreadLocal

Here’s an example of code:

public class ThreadLocalDemo { private static ThreadLocal<Weapon> weaponThreadLocal = new ThreadLocal<Weapon>() { @Override protected Weapon initialValue() { return new Weapon(); }}; private static class Player extends Thread { @Override public void run() { weaponThreadLocal.get().level += ThreadLocalRandom.current().nextInt(5); System.out.println(getName() + " level: " + weaponThreadLocal.get().level); weaponThreadLocal.get().combatEff = weaponThreadLocal.get().level * 10; System.out.println(getName() + " combatEff: " + weaponThreadLocal.get().combatEff); } } public static void main(String[] args) { Player player1 = new Player(); Player player2 = new Player(); player1.start(); player2.start(); } private static class Weapon { int level; int combatEff; public Weapon() { level = 1; combatEff = 10; }}}Copy the code

In the code above, there are two players, they play a game, each of them will have a Weapon at the beginning of the game. This weapon is fair to everyone at the start of the game, and its level and Combat Effectiveness are fixed (initialized by ThreadLocal). As the game progresses, their weapon levels will increase and they will become more powerful, but how much they will increase and become more powerful is up to fate (random number generated by ThreadLocalRandom).

Take a look at the results:

Thread-1 level: 3
Thread-0 level: 5
Thread-1 combatEff: 30
Thread-0 combatEff: 50
Copy the code

Thread 0 seems to have better luck.

The above example is just a way to prepare for the following illustration. Let’s start by talking about the relationship between threads and ThreadLocal.

In general, ThreadLocal can be considered a solution to the problem of shared variables between threads, that is, ThreadLocal maintains a copy of a shared variable for each thread, and that multiple threads can modify this variable (in effect, modify their own copies of the variable) with no thread-safety issues and high efficiency. So, in the example above, ThreadLocal provides two functions for a shared variable:

  • Set the initial value uniformly
  • Each thread does not affect the modification of this value, so that variables are isolated

At first glance, the relationship between threads and ThreadLocal looks like this:

threadLocal-key-value.png

However, this is not the case. If this is the case, the large section of the threadLocals property in the Thread is useless.

ThreadLocal and value are stored as key-value pairs in ThreadLocalMap. ThreadLocal and value pairs are stored as key-value pairs in ThreadLocalMap.

thread-threadLocal.png

When we call ThreadLocal’s get(), set(), and remove() values for Thread, It is the ThreadLocalMap of a Thread that operates on a value corresponding to a ThreadLocal.

Corresponding to the code examples above, if we added a shared Life variable to each Player and a ThreadLocal to manage Life variables, they would look something like this:

add-life.png

The relationship between threads and ThreadLocal is now clear. Let’s analyze the memory leak problem in ThreadLocal.

Analysis of memory leaks in ThreadLocal

ThreadLocal memory leak: ThreadLocal memory leak: ThreadLocal memory leak: ThreadLocal memory leak

memory.png

Follow up from the figure above.

Analysis of a

As you can see from the figure above, threadLocals in Thread holds the ThreadLocalMap object, the Entry object. When the thread completes execution, the thread object is reclaimed and ThreadLocalMap is reclaimed. Since Entry holds a reference to the Weapon object, the value object, the value object is also reclaimed. With the exception of ThreadLocal objects, all objects are reclaimed as the thread completes execution, and everyone is happy, with no memory leaks.

Analysis of the two

Weapon will be reclaimed for Weapon Weapon if the thread is executing and the reference to the ThreadLocal object is set to null. For example, Weapon will be reclaimed for Weapon Weapon.

Once the ThreadLocal object reference is set to NULL, the ThreadLocal object will be reclaimed at the next YGC since the Entry object holds a weak reference to the ThreadLocal object. In this case, the key of the Entry object is empty and the value cannot be accessed. How to reclaim it? Get (), set(), remove() ThreadLocalMap automatically nulls the value with the key empty, and the value object can be reclaimed without leaking memory.

So where exactly do memory leaks exist?

Analysis of the three

When we use ThreadLocal, we usually use it as a private static variable. If a ThreadLocal is used as a member object, then each class that uses a ThreadLocal might create a ThreadLocal object, ThreadLocal uses ThreadLocalMap to manage threads and values. Multiple ThreadLocal objects do not make sense and waste memory.

On the other hand, if ThreadLocal is used as a static variable, it cannot be nulled. If a ThreadLocal cannot be empty, the weak reference mechanism cannot be triggered to reclaim the ThreadLocal object. The key in the Entry will not be empty, and the value object cannot be reclaimed by analysis two.

That’s where memory leaks come in.

To summarize two conditions for a memory leak:

  • ThreadLocalUsed as a static variable
  • The thread is not finished executing

In this case, the key-value pairs in ThreadLocalMap in the thread pile up, potentially causing a memory overflow problem.

The solution

If the thread is still executing, ThreadLocal calls its remove() method after its task is complete, which empties the key in the Entry and recycles the value object. ** (here the remove() method has yet to be studied) **, but it is used correctly.

Thread pool dirty data analysis

Again, look at the dirty data problem when ThreadLocal is used with thread pools. (In fact, ThreadLocal memory leaks most often occur when used with thread pools.)

From the above analysis, the thread completes execution, the thread object is reclaimed, and all problems do not exist.

If a thread is reusing in the thread pool and does not call the remove method, the value fetched from ThreadLocalMap when a thread is reusing a task is the value of the last task. At this point, if our thread does not call set() to reassign value every time it executes a task, the business logic must be wrong.

The solution

  • Thread pool reuse occurs when the thread’srun()Method to be calledThreadLocal 的 set()The method ofvalueTo assign a value
  • The thread ofrun()Method is finally calledThreadLocal 的 remove()methods