漫聊 ThreadLocal (内存泄漏,弱引用)

Posted 石头StoneWang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了漫聊 ThreadLocal (内存泄漏,弱引用)相关的知识,希望对你有一定的参考价值。

漫聊 ThreadLocal (内存泄漏,弱引用)

背景

本文漫聊 ThreadLocal,想到什么写什么。大概会谈到几个问题

  • 关于ThreadLocal 和线程同步
  • ThreadLocal 在 JDK 中的实现
  • ThreadLocal 的内存泄漏风险,以及关于它弱引用,讨论 “弱引用是引起内存溢出的原因吗?”
  • 如何避免内存泄漏的风险

1、关于ThreadLocal 和 线程同步

解决线程访问共享变量的问题,可以使用线程同步技术,无论是synchronized还是 JUC的lock。

使用synchronized和JUC的lock,本质是通过加锁的机制来保证共享数据被多个线程争抢的有序性。加锁自然会导致并发性能下降,但是只有一份数据从而节省了内存,所以这是 “以时间换空间” (以耗时的增加换取了资源的减少,资源比如:CPU、内存、磁盘等)

还有另外一个解决方法,是以 “空间换时间”。数据弄多份,不加锁,访问贼快,因为不存在争抢同一份资源的问题,所以也没有线程安全的问题。所谓的 “空间换时间” 就是指耗费更多的内存减少了争抢的耗时。这就是ThreadLocal,将变量绑定到线程上,每个线程独有一份,多线程情况下不存在共享变量被争抢。

2、各版本的JDK的ThreadLocal 实现

大概有两种,JDK8一种,8之前一种。

  • JDK8
    在这里插入图片描述

  • 之前
    在这里插入图片描述

jdk8 这么做的好处,它为什么这么改?
肯定是更有好处才这么优化

  • 减少了哈希冲突和ThreadLocalMap扩容

    以前是ThreadLocal维护ThreadLocalMap,key是线程对象,这会导致很多线程的时候,往ThreadLocalMap塞,这个map就很长,也容易出现哈希冲突、并且需要扩容。现在改成Thread去维护ThreadLocalMap,而key是ThreadLocal对象,我们一般不会去new这么多ThreadLocal,减少了哈希冲突和扩容的问题。

3、关于内存泄漏

先说结论:

  1. ThreadLocal 用得不好,会引起内存泄漏。

    其实jdk的代码已经在极力挽救,有许多的措施防止内存泄漏,但是不能完全避免这种可能性。

  2. 弱引用不是导致内存泄漏的原因,相反,弱引用还促进了"内存节约"。

    这里的 “弱引用” 是指ThreadLocalMap 中的 Entry 的 key对 ThreadLocal 对象的引用是弱引用。

为什么说弱引用不是导致内存泄漏的原因?

1) 我们先假设用的是强引用

在这里插入图片描述

假设 threadLocal用完了,那么栈中 “threadLocal引用” 对于堆中的 “ThreadLocal对象” 的强引用就消失了。

由于线程是在线程池复用的,线程不会被回收。所以如果在Entry不手动remove的话,就永远存在value对 “你存的值” 的强引用,并且也存在 key 对 “ThreadLocal对象” 的强引用(假设用强引用的话),导致这些对象都不能被回收。因此累积多了就可能造成内存泄漏。

如果key对ThreadLocal对象是弱引用,在这种情况下ThreadLocal对象会被回收,key变成null。反而能促进节约内存。

所以key 对 “ThreadLocal对象” 的强引用并不是造成内存泄漏的原因。(反而还是有利的)

用弱引用还有个目的,就是ThreadLocal对象被回收后key的值是null,这是一个信号,表示value也可以被回收了,而Josh Bloch 和 Doug Lea确实做了优化:在多个方法,ThreadLocal#set里判定如果key是null就清掉无用的value

就算是有了上述这么多层保障,如果你不手工remove掉Entry,其value所强引用的对象还是有机会逃过被回收的(比如长时间不触发 ThreadLocal#set)

  • 有人说value也用弱引用行吗?肯定不行了,发生GC后你就无法ThreadLocal#get回你的值了!

  • 弱引用的特性:如果有个对象,只有弱引用引用它,则不管内存是否足够,发生GC,这个对象就会被回收。

既然用弱引用也不能完全解决内存泄漏,那为什么要用呢?

因为不能完全解决,也部分解决了。

  • 首先是因为使用弱引用导致了ThreadLocal对象可以被回收了
  • 其次更加重要的是,Josh Bloch 和 Doug Lea 用弱引用,是因为ThreadLocal被回收后,Entry的key就是null了,在代码的很多地方,检测到key是null就会把整个Entry移除,这就促进value被回收了。其实就是利用了弱引用导致key变成null,成为一个可以回收的标记或者说信号

上面提到的 “在代码的很多地方” 比如set方法和remove方法,好多地方都会触发检查Entry的key是不是null

4、怎么避免内存泄漏的隐患

  • ThreadLocal 最好当然是局部变量了,方法运行完立即就无强引用了。
  • ThreadLocal 在大多数的时候应该是避免不了要用做成员变量的,比如一般在拦截器里头,那就一定要remove,最好是在finally里进行remove(也可以在spring的拦截器,在无论是否出现异常都会被执行的接口内执行remove)
  • 最好是不要将ThreadLocal 用作静态变量

以上是关于漫聊 ThreadLocal (内存泄漏,弱引用)的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal弱引用与内存泄漏分析

ThreadLocal弱引用与内存泄漏分析

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal源码分析理解弱引用和内存泄漏

ThreadLocal 内存泄露的根本原因