ThreadLocal 里面存储的数据,它的生命周期是和线程或者线程池的生命周期保持一致的,如果在整个程序的运行期间,线程和线程池都没有销毁的情况下,那么 ThreadLocal 里面的数据也不会被销毁,也不会被垃圾回收器所回收,这个就是内存泄露问题。当程序中有大量的数据,并且 ThreadLocal 里面所记录的都是大数据的情况下,那么持续的内存泄漏就有可能会造成内存溢出(OutOfMemory)。
Thread 源码中,每个线程 Thread 都拥有一个数据存储存储容器 ThreadLocalMap,在执行 ThreadLocal.set 方法的时候,会将存储的值放到 ThreadLocalMap 容器中,ThreadLocalMap 中有一个 Entry[] 数组用来存储所有的数据,此处的 Entry[] 可以理解为哈希桶。
Thread -> ThreadLocalMap -> Entry -> key,Value
引用类型分为 4 种 :强引用、软引用、弱引用、虚引用(几乎不用)
- 强引用无法被 GC 回收
- 软引用一般不会被 GC 回收,除非内存不够用了,触发了 full GC 了,那么才可能会回收软引用。
- 弱引用会被 GC 回收
为什么是一直持有 value ?
因为 ThreadLocal 在设计的时候,Entry 中的 key 使用的是弱引用,而 value 使用的是强引用。
如果 ThreadLocal 没有被直接引用(外部强引用),那么在垃圾回收的时候,由于 ThreadLocalMap 中的 key 是弱引用,所以 key 一定会被回收,因此 ThreadLocalMap 中就会出现 key 为 null 的 Entry,并且没有办法访问这些数据,那么强引用链 Thread -> ThreadLocalMap -> Entry -> value 就会一直存在,导致 value 无法被 GC 回收,从而导致内存泄漏。
为什么 ThreadLocal 在设计的时候,Entry 中的 key 使用弱引用,value 使用强引用 ?
这是从 ThreadLocal 的性能和防止内存溢出这两方面综合考量的,如果说程序后面还需要使用 ThreadLocalMap,那么当插入的键值对的 value 已经存在时,就不需要重新进行插入了,只需要恢复当前键值对的 key 即可。
如何解决 ThreadLocal 内存泄漏问题 ??
只需要在使用完 ThreadLocal 之后,调用它的 remove 方法就可以避免内存泄漏问题了。