Java中的ThreadLocalMap是ThreadLocal类实现线程本地变量存储的核心数据结构,每个线程内部都持有一个ThreadLocalMap实例,用来存储当前线程对应的所有ThreadLocal变量的值。ThreadLocalMap内部采用自定义哈希表的结构,其中的节点是Entry类型,而Entry的key被设计为弱引用,这一设计是ThreadLocal实现中非常重要的部分。

ThreadLocalMap的基本结构
ThreadLocalMap的结构和普通的HashMap有相似之处,但又有自己的特性。它内部维护一个Entry数组,每个Entry对象用来存储一个ThreadLocal变量和对应的线程本地值。Entry的定义如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
// 线程本地存储的值
Object value;
// 构造方法,第一个参数是key也就是ThreadLocal实例,第二个是对应的值
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可以看到Entry继承了WeakReference,并且泛型参数是ThreadLocal类型,这意味着Entry的key也就是ThreadLocal实例是弱引用类型。而value是直接用强引用存储的Object类型。
为什么Entry的key要设计成弱引用
要理解弱引用的设计初衷,首先需要明确强引用和弱引用的回收区别。强引用是平时最常见的引用类型,只要强引用存在,垃圾回收器就不会回收被引用的对象。而弱引用的生命周期更短,当发生垃圾回收时,无论当前内存是否充足,都会回收掉只被弱引用关联的对象。
避免ThreadLocal实例无法被回收
如果Entry的key使用强引用,那么当我们在业务代码中不再使用某个ThreadLocal实例时,比如把ThreadLocal的引用置为null,此时ThreadLocal实例只有一个引用就是ThreadLocalMap中Entry的key强引用。因为线程可能长期运行不会结束,ThreadLocalMap会一直持有这个强引用,导致ThreadLocal实例无法被垃圾回收,造成内存泄漏。
而使用弱引用之后,当业务侧的ThreadLocal强引用被移除,那么ThreadLocal实例就只剩下Entry中弱引用的关联,下一次垃圾回收发生时,ThreadLocal实例就会被回收,key会变成null,避免了ThreadLocal实例本身的内存泄漏。
配合后续清理机制释放value
Entry的key被回收变成null之后,ThreadLocalMap在设计时也配套了清理机制。在调用get、set、remove等方法时,ThreadLocalMap会遍历数组,将key为null的Entry对应的value也设置为null,同时把Entry本身从数组中移除,这样就释放了value占用的内存。
我们可以通过ThreadLocal的get方法的部分逻辑看到这个清理过程:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 获取对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 初始化值
return setInitialValue();
}
// ThreadLocalMap中的getEntry方法
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
// 找不到时触发清理逻辑
return getEntryAfterMiss(key, i, e);
}
// 清理key为null的Entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
// 清理key为null的Entry
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
使用弱引用仍需要注意的问题
虽然弱引用设计解决了ThreadLocal实例本身的内存泄漏问题,但value是强引用,如果之后线程一直运行,且没有再调用ThreadLocal的get、set、remove方法,那么key为null的Entry的value就不会被清理,还是会造成内存泄漏。因此在实际开发中,当我们不再使用某个ThreadLocal变量时,最好主动调用remove方法,手动触发清理逻辑,避免value长期占用内存。
总结
ThreadLocalMap中Entry的key采用弱引用设计,核心初衷是为了避免ThreadLocal实例因为被ThreadLocalMap强引用而无法被回收,减少内存泄漏的风险。同时配合ThreadLocalMap内部的清理机制,可以在后续操作中回收key为null的Entry对应的value内存。不过这一设计并不能完全避免内存泄漏,开发者仍需要在合适的时候主动调用remove方法,确保value也能被正确释放。
ThreadLocalMapThreadLocal弱引用Entry修改时间:2026-06-28 21:33:18