天天看点

JVM--Java内存泄露--ThreadLocal--原因/解决方案

简介

        本文介绍为什么ThreadLocal会引起内存泄露以及如何避免ThreadLocal内存泄露。

        之前介绍过​​ThreadLocal的原理​​,本文不再赘述。

        本内容也是Java面试时经常会问到的问题。

key内存泄露的避免

JVM--Java内存泄露--ThreadLocal--原因/解决方案

        每个 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有了这个防护措施,也可能内存泄露,场景如下:

  1. 使用static的ThreadLocal。
  1. 这延长了ThreadLocal的生命周期
  1. 不再使用某个ThreadLocal时没有调用get(),set(),remove()方法。
  1. 比如:我们使用一个ThreadLocal来set()了很多数据,然后不再使用这个ThreadLocal。然后来了一次垃圾回收,会将各个ThreadLocalMap 中的这个ThreadLocal(作为key)释放掉。这时很多个线程中的ThreadLocalMap都存储着这个key为null的Entry,导致内存泄露。

如何避免value内存泄露

当前线程使用完 Threadlocal 后,调用这个 ThreadLocal 的 remove() 方法进行清除。

其他网址