ThreadLocal本身不存储数据,所有值都存于当前线程的ThreadLocalMap中;该Map是Thread对象的成员字段threadLocals,采用弱引用key+强引用value的Entry数组实现,通过线性探测解决哈希冲突。

ThreadLocal 本身不存数据,所有变量值都落在当前线程的 ThreadLocalMap 里,而这个 Map 就是线程对象(Thread)的一个成员字段 —— threadLocals。理解它的内存布局,关键在于看清 Thread、ThreadLocal、ThreadLocalMap 和 Entry 四者之间的归属与引用关系。
Thread 对象持有 ThreadLocalMap 实例
每个 Thread 实例内部都有一个包私有的字段:
ThreadLocal.ThreadLocalMap threadLocals;
这个字段默认为 null,只有在第一次调用 set() 或 get() 时才会被初始化。也就是说,线程刚启动时并不占用额外内存;只有真正使用了 ThreadLocal,才按需分配自己的 Map。
ThreadLocalMap 是数组驱动的定制哈希表
它底层是一个 Entry[] table 数组,初始容量为 16,扩容阈值是容量的 2/3(约 10.6 → 向下取整为 10)。和 HashMap 不同,它不用链表或红黑树处理冲突,而是采用线性探测法:当发生哈希冲突,就往后逐个查找空槽位,直到插入成功。
- Entry 是静态内部类,继承自
WeakReference<ThreadLocal<?>> - key 是当前 ThreadLocal 实例,但被包装成弱引用
- value 是用户存入的对象,是强引用
Entry 的内存结构细节
一个 Entry 对象在堆中实际包含三部分:
- 对象头:含 Mark Word 和 Class Pointer(JVM 规范定义,与具体版本相关)
- referent 字段(来自 WeakReference):指向 ThreadLocal 实例,但不阻止 GC 回收
- value 字段:直接持有业务数据,比如 String、UserContext、Connection 等
当外部不再持有某个 ThreadLocal 引用时,GC 会回收其 referent,Entry 的 key 变成 null,该 Entry 称为 stale entry(陈旧条目)。此时 value 仍被强引用着,若不清理,就会造成内存泄漏。
为什么 key 设计为弱引用?
这不是为了“引发”内存泄漏,而是为了降低泄漏风险:
- 如果 key 是强引用:ThreadLocal 实例无法被回收,Entry 长期驻留,value 永远无法释放
- 如果 key 是弱引用:ThreadLocal 被回收后,key 变 null,后续在
set/get/remove过程中触发探测清理(expungeStaleEntry),value 才有机会被回收
注意:清理不是即时发生的,也不是每次操作都执行。真正可靠的清理方式,是业务侧主动调用 ThreadLocal.remove() —— 它会立即触发 stale entry 扫描与 value 置 null。

















