ThreadLocal真会内存泄露?

最近心血来潮花了一周多时间把周克明先生的《深入理解JAVA虚拟机》的书看完了,没想到自己竟然能这么快的看完,虽不说全部掌握,但绝大部分都已经理解,想想大半年前自己的状态,这半年进步神速,这应该得益于现在的心境,而现在我也会开始写博客,因为我觉得能够很好的把自己掌握的东西表达出来才是真的理解和掌握。

ThreadLocal的内存泄露

前提

在看完书后对JVM产生了浓重的兴趣,我也开始搜寻更多的question去试着解答,所以近期在oschina看到一篇关于“ThreadLocal内存泄露”的文章,于是开始研究ThreadLocal是否会导致内存泄露的问题, 文章的笔者分析的非常好,但是有一个最核心的问题没有注意到,导致得出的结果恰恰相反,并且其中有一段关键的话是让人听得有点含糊的,笔者在这里把实现原理记录下来,说的有出入的地方,也欢迎更多的朋友拍砖指正。

分析ThreadLocal

ThreadLocal内存泄露”文中有一段话:

“每个key都弱引用指向threadlocal. 像上面code中的例子,当把threadlocal实例tl置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用”

上段话总结的比较精致,但问题真实如此吗?笔者分析源代码把自己的理解记录下来,也让后面的读者一起学习: 其实ThreadLocal作为key断掉强引用t1之后 value是会被GC的,其原因是因为ThreadLocalMap中的Entry 继承了 WeakReference(注意是继承,说明Entry本身就是一个弱引用),我们再来看看源码:

static class Entry extends WeakReference<ThreadLocal> {
        Object value;

        Entry(ThreadLocal var1, Object var2) {
            super(var1);
            this.value = var2;
        }
 }

再看下ThreadLocal实现原理图,虚线表示弱引用,实线表示强引用。

143024_jRew_254689.png

注意:文章中分析WeakReference的泛型是ThreadLocal对象,并没有包含Entry中的Object value, 且后续并没有再用到这个ThreadLocal,所以ThreadLocal=null后没有任何强引用所以会被GC掉,但正如笔者前面标出的细节,ThreadLocalMap里面的Entry实际上是继承了WeakReference的,所以Entry本身就是个弱引用对象,所以当Entry中的Key ThreadLocal=null被GC掉后,并且value除了身为WeakReference的Entry对象的引用外不存在其他强引用的情况下,整个Entry是可能会被GC掉的。也就不存在因为value而内存泄漏了。(另外如果不知道WeakReference的,可以去看下Java WeakReference的理解与使用这篇文章)

思考:这个完美的设计来自是为了保证什么,或者说为什么这样设计?(这里不再详细的介绍ThreadLocal的实现)

首先ThreadLocalMap是存在每个线程里面的私有变量(极可能分配在TLAB中),Map中的Entry的key为ThreadLocal对象,考虑两个问题:

1、用户不想用这个对象了,直接把ThreadLocal=null,则ThreadLocal被GC,那么线程中value怎么回收?也就是ThreadLocalMap的Entry中的key为空了,value怎么办?(上面提到过ThreadLocalMap为私有变量,访问不到)

答:所以设计者把整个Entry都是WeakReference对象。当ThreadLocal被回收,则Entry没有强引用就可以直接被GC,保证了不会因为value而导致内存泄漏。

2、因为WeakReference<ThreadLocal>,所以即便用户代码中不执行ThreadLocal=null,而是代码中很少几率会用到,或者申明后就没用到过,那么在内存吃紧的时候,JVM也可以直接把Entry回收,那如果后期又要用ThreadLocal了,而ThreadLocalMap没有怎么办呢?

答:这个问题其实很简单。这样设计好处是为了当内存吃紧而编译分析ThreadLocal暂时用不到之后,可以直接GC掉从而释放部分内存。那如果后面又需要的时候怎么办呢?还记得ThreadLocal的get方法怎么写的嘛?如果get不到 是不是要调用SetInitialValue()方法重新生成?此举也可以在内存吃紧的时候缓解一部分压力。

所以。。。。膜拜JDK大神的设计吧。。简直66666

另外总结下:WeakReference、SoftReference这些个非强引用的引用,通常可以用来做缓存的对象,一般情况下我们本地缓存随着引用越来越多,导致了缓存的生命周期与应用的生命周期相同,当然我们通常通过LRU、限制个数等各种算法来避免缓存膨胀,但其实更好的做法是,在内存吃紧的时候缓存是可以被GC的,也就是整个缓存的大小是弹性的,在内存吃紧的时候我可以全部走DB或其他,这样可以减缓线上因为内存问题OOM。

 

注:版权所有转载请注明出处http://my.oschina.net/u/926166/blog/539228

 

转载于:https://my.oschina.net/ij5IYLKW/blog/539228

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值