专栏首页java一日一条关于ThreadLocal内存泄露的备忘

关于ThreadLocal内存泄露的备忘

还记得第一次接触到ThreadLocal可能导致内存泄露的问题是有一次面试的时候被问到了ThreadLocal的缺陷是什么。当然由于后来没有面试官的联系方式很遗憾也一直没能确认所谓的缺陷是不是就是可能导致内存泄漏,不过后来发现虽然当时弄明白了可是过段时间又搞忘记了这个问题,所以特别记录下来做个备忘吧。

ThreadLocal从名字上来说就很好理解,就是用于线程(Thread)私有(Local)的存储结构,这种结构能够使得线程能够使用只有自己能够访问和修改的变量,从而实现多个线程之间的资源互相隔离,达到安全并发的目的。

也因此,ThreadLocal作为线程并发中的一种资源使用方式,得到了很广泛的应用,比如Spring MVC、Hibernate等。 不过值得一提的是,通常有人会讲ThreadLocal和synchronised等放在一起,作为形成安全并发的手段之一。其实我觉得这是比较容易使人误导的,因为两者的目的性完全不一样。 ThreadLocal主要的是用于独享自己的变量,避免一些资源的争夺,从而实现了空间换时间的思想。 而synchronised则主要用于临界(冲突)资源的分配,从而能够实现线程间信息同步,公共资源共享等,所以严格来说synchronised其实是能够实现ThreadLocal所需要的达到的效果的,只不过这样会带来资源争夺导致并发性能下降,而且还有synchronised、线程切换等一些可能不必要的开销。


对于ThreadLocal而言,其实使用起来有点像基础类型的装箱类型的感觉(个人觉得其实也可以算是一种装饰器模式的使用?),具体的使用就不在啰嗦了。下面就看看这次备忘的重点,如何导致内存泄漏的。

[ThreadLocal可能引起的内存泄露](http://www.cnblogs.com/onlywujun/p/3524675.html)

所以简单的说,主要原因就是在于TreadLocal中用到的自己定义的Map(和常用的Map接口不同)中,使用的Key值是一个WeakReference类型的值(弱引用会在下一次GC时马上释放而不管是否被引用)。那么如果这个Key在GC时被释放了,就会导致Value永远都不会被调用到,但是如果线程不结束,又一直存在。

因为可能不熟悉这部分内容的同学(例如几周以后的我)会感觉有点迷糊为什么这个图是这样的,就具体再解释一下细节点:

  • 首先当然是看一下我们的主角ThreadLocal类,只保留了几个重点的地方,特别的是内部静态类的ThreadLocalMap是ThreadLocal自己实现的一个Map,而这个Map用使用了ThreadLocal作为了一个弱引用的Key(也就是主要问题点)。 p.s.不知道各位第一次看的时候会不会跟我一样有种我是老子的儿子的同时又是老子的老子的感觉,哈哈哈

接着不得不说的就是我们的大佬Thread类,里面关于ThreadLocal部分的内容主要是这样滴。我们可以看到这里主要是声明了ThreadLocal里面的Map作为类变量来提供给线程使用的。也正式因为如此,才会在ThreadLocal里面的getMap方法是拉取的Thread里面的Map。 p.s. 感觉确实有点绕

  • 于是到这里我们就明白了,其实每个Thread里面都有一个Map,Map里面的Key是ThreadLocal类的一个实例,之所以会比较混淆主要还是因为这里的Map又是ThreadLocal里面的一个内部静态类。

所以到这里其实有两个问题是暂时还没想通的,也希望有各位大佬指点一二:

  1. TreadLocalMap 其实是可以抽取成单独的类的?这样就使得逻辑和嵌套关系没有这么绕的感觉。
  2. 为什么只有Key要设计成WeakReference而不是Key和Value都是,或者这里为什么要设置弱引用?如果为了保护内存空间其实两者都是弱引用更好吧,是不是有什么其它考虑?

回归到内存泄露是因为WeakReference Key的问题,当然,Java的各位大佬肯定早就想到这个问题了,可以看到人家注释里面是这么说的,大意就是如果key==null的时候,就可以认为这个值无效了,可以调用expunged进行清理:

而这个expungeStaleEntry方法在get、set时都会有间接的调用,而且remove方法中也会显示的调用,这也就是为什么有的文章中说通过在线程调用完成之后,通过调用remove方法能有效的杜绝该泄露问题的原因。

当然简单来说理解到这里就基本明了内存泄露的原因,但是其实再深入一点来说,如果泄露的原因是Key被释放,而Value没有释放,那么是否一定会有泄露呢? 答案当然是否定的,因为如果是一般的线程场景中,除了会调用expungeStaleEntry来进行清理,最差,在线程结束之时,自然也就消除了引用从而使得Value得以GC回收。

所以,会不会有线程一直不结束的场景呢? 当然答案是肯定的,最简单来说线程只要一直在wait就不会结束了,不过这种场景下其实和泄露也没啥关系的感觉。 其实最常用的线程一直不结束的场景,自然就是线程池了。因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。

本文分享自微信公众号 - java一日一条(mjx_java)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-17

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java 线程池的理论与实践

    前段时间公司里有个项目需要进行重构,目标是提高吞吐量和可用性,在这个过程中对原有的线程模型和处理逻辑进行了修改,发现有很多基础的多线程的知识已经模糊不清,如底层...

    哲洛不闹
  • Java 编程要点之并发(Concurrency)详解

    计算机用户想当然地认为他们的系统在一个时间可以做多件事。他们认为,他们可以工作在一个字处理器,而其他应用程序在下载文件,管理打印队列和音频流。即使是单一的应用程...

    哲洛不闹
  • JAVA多线程和并发基础面试问答

    多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应...

    哲洛不闹
  • 【死磕Java并发】-----深入分析ThreadLocal

    ThreadLoacal是什么? ThreadLocal是啥?以前面试别人时就喜欢问这个,有些伙伴喜欢把它和线程同步机制混为一谈,事实上ThreadLocal与...

    用户1655470
  • 深入分析ThreadLocal

    ThreadLocal是啥?以前面试别人时就喜欢问这个,有些伙伴喜欢把它和线程同步机制混为一谈,事实上ThreadLocal与线程同步无关。ThreadLoca...

    黄泽杰
  • Java的wait()、notify()学习三部曲之一:JVM源码分析

    综述 Java的wait()、notify()学习三部曲由三篇文章组成,内容分别是: 一、通过阅读openjdk8的源码,分析和理解wait,notify在...

    程序员欣宸
  • Java并发编程--Lock

      Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。synchronized方法或代码块的使用提供了对与每个对象相关的...

    在周末
  • Java的wait和notify学习三部曲之一:JVM源码分析

    Java的wait()、notify()学习三部曲由三篇文章组成,内容分别是:一、通过阅读openjdk8的源码,分析和理解wait,notify在JVM中的具...

    程序员欣宸
  • 基于AQS原理实现的锁

    你好,我是疾风先生,先后从事外企和互联网大厂的java和python工作, 记录并分享个人技术栈,欢迎关注我的公众号,致力于做一个有深度,有广度,有故事的工程师...

    keithl
  • JAVA-LOCK之底层实现原理(源码分析)

    首先和Synchronized(可以参考) 的不同之处,Lock完全用Java写成,在java这个层面是无关JVM实现的。其实现都依赖java.util.con...

    海涛

扫码关注云+社区

领取腾讯云代金券