简介
本文介绍为什么ThreadLocal会引起内存泄露以及如何避免ThreadLocal内存泄露。
之前介绍过ThreadLocal的原理,本文不再赘述。
本内容也是Java面试时经常会问到的问题。
key内存泄露的避免
每个 Thread 里都有一个 ThreadLocalMap,而 ThreadLocalMap 中真正承载数据的是一个 Entry 数组,Entry 的 key 是 threadlocal 对象的弱引用。
Entry 的 key 是 threadlocal 对象的弱引用,这是为了避免key的内存泄漏。(弱引用:垃圾回收时,无论内存是否足够,都会回收弱引用对象。详见:这里)
为什么Entry的key是弱引用可以避免内存泄露呢?原因是:Entry的key是弱引用,这样当我们程序里不再使用这个ThreadLocal的时候,垃圾回收时就能够将Entry里的这个ThreadLocal这个key回收掉了。假如是强引用,那么就算我们不再使用此ThreadLocal,但由于Entry的key还持有此ThreadLocal的强引用,垃圾回收时就无法回收此ThreadLocal,那么就导致了内存泄露。
value内存泄露的原因
上边已经说过了,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来使用它,那么垃圾回收时这个ThreadLocal会被回收。结果就是:ThreadLocalMap中出现key为null的Entry,就无法访问这些 key 为 null 的 Entry 的 value,这些 value 被 Entry 对象引用,所以value所占内存不会被释放。
思考一下:我们是通过ThreadLocal类型的key去使用对应的value的,当Entry里某个ThreadLocal类型的key变成null的时候,实际就是我们不再使用这个ThreadLocal了,那么我们也就用不到它所对应的value了,但这个value还占着内存,这不就是内存泄露了嘛。这个value因为存在着这样一条强引用链导致不能被回收:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value。
ThreadLocalMap已经考虑到这种情况,加了防护措施:ThreadLocal进行get(), set(), remove()时会清除线程ThreadLocalMap里所有key为null的value。
虽然ThreadLocal有了这个防护措施,也可能内存泄露,场景如下:
-
- 使用static的ThreadLocal。
- 这延长了ThreadLocal的生命周期
- 不再使用某个ThreadLocal时没有调用get(),set(),remove()方法。
- 比如:我们使用一个ThreadLocal来set()了很多数据,然后不再使用这个ThreadLocal。然后来了一次垃圾回收,会将各个ThreadLocalMap 中的这个ThreadLocal(作为key)释放掉。这时很多个线程中的ThreadLocalMap都存储着这个key为null的Entry,导致内存泄露。
如何避免value内存泄露
当前线程使用完 Threadlocal 后,调用这个 ThreadLocal 的 remove() 方法进行清除。