ThreadLocal理论以及其内存泄漏的问题

本人的另一个关于ThreadLocal理论和实践的文章:《ThreadLocal实现线程范围内数据共享》

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。

具体原理

ThreadLocal里类型的变量,其实是放入了当前Thread里。每个Thread都有一个{@link Thread#threadLocals},它是一个map:{@link java.lang.ThreadLocal.ThreadLocalMap}。这个map的entry是{@link java.lang.ThreadLocal.ThreadLocalMap.Entry},具体的key和value类型分别是{@link ThreadLocal}和 {@link Object}。

(注:实际是ThreadLocal的弱引用WeakReference<ThreadLocal<?>>,但可以先简单理解为ThreadLocal。)

当设置一个ThreadLocal变量时,这个map里就多了一对ThreadLocal -> Object的映射。

在这里插入图片描述

常见的ThreadLocal用法有

  • 存储单个线程上下文信息。比如存储id等;
  • 使变量线程安全。变量既然成为了每个线程内部的局部变量,自然就不会存在并发问题了;
  • 减少参数传递。比如做一个trace工具,能够输出工程从开始到结束的整个一次处理过程中所有的信息,从而方便debug。由于需要在工程各处随时取用,可放入ThreadLocal。

为啥用弱引用

不妨反过来想想,如果使用强引用,当ThreadLocal对象(假设为ThreadLocal@123456)的引用(即:TL_INT,是一个强引用,指向ThreadLocal@123456)被回收了,ThreadLocalMap本身依然还持有ThreadLocal@123456的强引用,如果没有手动删除这个key,则ThreadLocal@123456不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收,可以认为这导致Entry内存泄漏。

那使用弱引用的好处呢?

如果使用弱引用,那指向ThreadLocal@123456对象的引用就两个:TL_INT强引用,和ThreadLocalMap中Entry的弱引用。一旦TL_INT被回收,则指向ThreadLocal@123456的就只有弱引用了,在下次gc的时候,这个ThreadLocal@123456就会被回收。

带来的问题:

那么问题来了,ThreadLocal@123456对象只是作为ThreadLocalMap的一个key而存在的,现在它被回收了,但是它对应的value并没有被回收,内存泄露依然存在!而且key被删了之后,变成了null,value更是无法被访问到了!针对这一问题,ThreadLocalMap类的设计本身已经有了这一问题的解决方案,那就是在**每次get()/set()/remove()ThreadLocalMap中的值的时候,会自动清理key为null的value。**如此一来,value也能被回收了。

既然对key使用弱引用,能使key自动回收,那**为什么不对value使用弱引用?**答案显而易见,假设往ThreadLocalMap里存了一个value,gc过后value便消失了,那就无法使用ThreadLocalMap来达到存储全线程变量的效果了。(但是再次访问该key的时候,依然能取到value,此时取得的value是该value的初始值。即在删除之后,如果再次访问,取到null,会重新调用初始化方法。)

总结

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收,但是该key对应的value是强引用,value不会被回收。
由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏。

解决:
每次使用完ThreadLocal,都**调用它的remove()**方法,清除数据。
在这里插入图片描述
在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就清理。

附上四大引用

强引用:new,普通的引用,强引用指向的对象即使出现OOM不会被回收;
 如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收;
软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
SoftReference的特点是它的一个实例保存对一个Java对象的软引用, 该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。

也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对 这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用。

另外,一旦垃圾线程回收该Java对象之 后,get()方法将返回null。
弱引用:仅有弱引用指向的对象,只要发生gc就会被回收。

在java中,用java.lang.ref.WeakReference类来表示。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被JVM回收,这个软引用就会被加入到与之关联的引用队列中。

虚引用:(PhantomReference)虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。
在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

部分转自:https://blog.csdn.net/puppylpg/article/details/80433271

猜你喜欢

转载自blog.csdn.net/mulinsen77/article/details/89631031
今日推荐