Backend Development 9 min read

Understanding ThreadLocal Implementation and Memory Leak Issues in Java

This article explains how ThreadLocal stores values per thread, details the internal set and get mechanisms, discusses potential memory‑leak problems caused by stale entries, and provides best‑practice guidelines such as using remove() and InheritableThreadLocal for child threads.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding ThreadLocal Implementation and Memory Leak Issues in Java

ThreadLocal stores a value that is exclusive to the current thread, ensuring isolation between threads when concurrently modifying the same variable.

To use ThreadLocal, you can directly call set(T value) and get() to set and retrieve the thread‑local value.

set method

public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
    if (map != null) { // 如果map不是空
        map.set(this, value); // 设置值
    } else {
        createMap(t, value); // 创建并设置值
    }
}

// 获取线程的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 对该ThreadLocal设置值
private void set(ThreadLocal
key, Object value) {
    // ThreadLocalMap内部的table数组
    Entry[] tab = table;
    int len = tab.length;
    // 根据threadLocal的hash和长度进行与运算,找到下标位置
    int i = key.threadLocalHashCode & (len-1);

    // 曾经该threadLocal有值,设置值并返回
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        // 获取entry的引用
        ThreadLocal
k = e.get();
        // 引用等于当前threadLocal 则进行设置值
        if (k == key) {
            e.value = value;
            return;
        }
        // 当前引用为空,把key、value组装成entry放到i位置上,并清楚key为空的entry
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    // 组装entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 如果没有元素被清楚,并当前数组大小大于threshold则进行rehash;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

get method

public T get() {
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // this为当前threadLocal,获取对应的entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 返回当前entry的值即可。
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 设置初始值并返回,初始值是null
    return setInitialValue();
}

private Entry getEntry(ThreadLocal
key) {
    // 查找下标
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        // 开始遍历entry数组,如果能找到key的entry就返回否则返回null
        return getEntryAfterMiss(key, i, e);
}

The internal structure of ThreadLocal consists of a per‑thread ThreadLocalMap that holds Entry objects, each containing a weak reference to the ThreadLocal key and a strong reference to the value.

Potential Memory Leak

When a ThreadLocal instance becomes unreachable, its key is only weakly referenced in the map; however, if the corresponding entry is not cleared, the value remains strongly referenced, potentially causing a memory leak. The cleanup occurs during set or remove calls, which invoke cleanSomeSlots and rehash as needed.

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

Therefore, after using a ThreadLocal, it is recommended to call remove() to explicitly clear the entry.

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

Defining ThreadLocal as a private static variable ensures the strong reference to the ThreadLocal itself remains, allowing the map to locate and clean up entries reliably.

Can a Child Thread Use the Parent Thread's ThreadLocal Value?

No, a regular ThreadLocal does not propagate values to child threads. To inherit the value at thread creation, use InheritableThreadLocal , which copies the parent’s value to the child only once during initialization.

private static InheritableThreadLocal threadLocal = new InheritableThreadLocal();

@SneakyThrows
public static void main(String[] args) {
    threadLocal.set("1");
    Thread thread = new Thread(
            () -> {
                System.out.println("子线程获取threadLocal的值为:" + threadLocal.get());
                threadLocal.set("2");
            }
    );
    thread.start();
    Thread.sleep(200);
    System.out.println("父线程获取threadLocal的值为:" + threadLocal.get());
}

In this example, the child thread initially sees the parent’s value ("1"), can change its own copy to "2", while the parent’s value remains unchanged.

Demonstration Code

public static void main(String[] args) {
    for (int i = 0 ; i < 100 ; i ++){
        ThreadLocal temp = new ThreadLocal();
        temp.set(i);
        temp = null;
    }
    // System.gc();
    ThreadLocal m = new ThreadLocal();
    m.set("value");
}

Running this code with System.gc() enabled can trigger the cleanup of stale entries, illustrating how garbage collection interacts with ThreadLocal’s weak references.

JavaconcurrencythreadlocalMemoryLeakInheritableThreadLocalThreadLocalMap
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.