image.png
当把threadlocal变量置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收。这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而这块value永远不会被访问到了,所以存在着内存泄露。
只有当前thread结束以后,current thread就不会存在栈中,强引用断开,Current Thread、Map value将全部被GC回收。最好的做法是不在需要使用ThreadLocal变量后,都调用它的remove()方法,清除数据。


image.png

为什么使用弱引用而不是强引用?

key 使用强引用

对ThreadLocal对象实例的引用被置为null了,但是ThreadLocalMap还持有这个ThreadLocal对象实例的强引用,如果没有手动删除,ThreadLocal的对象实例不会被回收,导致Entry内存泄漏。

key 使用弱引用

对ThreadLocal对象实例的引用被被置为null了(局部变量声明的**ThreadLocal对象),由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal的对象实例也会被回收。value在下一次ThreadLocalMap调用set,get,remove都有机会被回收(ThreadLocal自我保护机制)
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是
使用弱引用可以多一层保障,弱引用ThreadLocal实例不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:
由于ThreadLocalMap的生命周期跟Thread一样长如果没有手动删除对应key**就会导致内存泄漏,而不是因为弱引用

为什么使用static final修饰?

  • static:节省空间
  • 避免被修改引用,造成读不到之前设置的值

:::tips

内存泄漏真正原因:正常使用需要static final修饰ThreadLocal变量,这样导致ThreadLocal变量永远不会回收,破坏了弱引用的规则,弱引用失效! 必须手动清除!

:::

为什么ThreadLocal实例除了添加static final 修饰之后,还常常加上了private修饰呢?

因为使用ThreadLocal会带来内存泄露的风险,所以需要避免ThreadLocal被外部类使用,可以提供封装的方法,供外部调用

如何回收弱引用?

JVM利用调用ThreadLocal的remove、get、set方法的时候,回收弱引用。
image.png

remove()

  1. try {
  2. ...
  3. ...
  4. ...
  5. } finally {
  6. tl.remove();
  7. }