浅谈Java中ThreadLocal内存泄露的原因及处理方式
作者:倔强的不服 时间:2021-06-12 21:08:37
1、ThreadLocal 使用原理
前文我们讲过ThreadLocal的主要用途是实现线程间变量的隔离,表面上他们使用的是同一个ThreadLocal, 但是实际上使用的值value却是自己独有的一份。用一图直接表示threadlocal 的使用方式。
图1
从图中我们可以当线程使用threadlocal 时,是将threadlocal当做当前线程thread的属性ThreadLocalMap 中的一个Entry的key值,实际上存放的变量是Entry的value值,我们实际要使用的值是value值。value值为什么不存在并发问题呢,因为它只有一个线程能访问。threadlocal我们可以当做一个索引看待,可以有多个threadlocal 变量,不同的threadlocal对应于不同的value值,他们之间互不影响。ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
2、ThreadLocal 内存泄露的原因
Entry将ThreadLocal作为Key,值作为value保存,它继承自WeakReference,注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个「弱引用」。可以看图1.
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
主要两个原因
1 . 没有手动删除这个 Entry
2 . CurrentThread 当前线程依然运行
第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法删除对应的 Entry ,就能避免内存泄漏。
第二点稍微复杂一点,由于ThreadLocalMap 是 Thread 的一个属性,被当前线程所引用,所以ThreadLocalMap的生命周期跟 Thread 一样长。如果threadlocal变量被回收,那么当前线程的threadlocal 变量副本指向的就是key=null, 也即entry(null,value),那这个entry对应的value永远无法访问到。实际私用ThreadLocal场景都是采用线程池,而线程池中的线程都是复用的,这样就可能导致非常多的entry(null,value)出现,从而导致内存泄露。
综上, ThreadLocal 内存泄漏的根源是:
由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏.
3、 为什么不将key设置为强引用
3.1 、key 如果是强引用
那么为什么ThreadLocalMap的key要设计成弱引用呢?其实很简单,如果key设计成强引用且没有手动remove(),那么key会和value一样伴随线程的整个生命周期。
1、假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了,但是因为threadLocalMap的Entry强引用了threadLocal(key就是threadLocal), 造成ThreadLocal无法被回收。在没有手动删除Entry以及CurrentThread(当前线程)依然运行的前提下, 始终有强引用链CurrentThread Ref → CurrentThread →Map(ThreadLocalMap)-> entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的。请结合图1看。
3.2 那么为什么 key 要用弱引用
事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的.这就意味着使用threadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaI 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏.
3.3 如何正确的使用ThreadLocal
1、将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
来源:https://blog.csdn.net/u010445301/article/details/124935802
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
一篇文章带你搞懂Java线程池实现原理
![](https://img.aspxhome.com/file/2023/5/77075_0s.jpg)
C#抽象类和接口的区别分析
Android Google AutoService框架使用详解
![](https://img.aspxhome.com/file/2023/3/89513_0s.png)
Java开发之spring security实现基于MongoDB的认证功能
SpringBoot如何实现定时任务示例详解
![](https://img.aspxhome.com/file/2023/3/84613_0s.png)
详解Http请求中Content-Type讲解以及在Spring MVC中的应用
Java 实现Excel文档添加超链接的代码
![](https://img.aspxhome.com/file/2023/7/88577_0s.png)
springboot项目中jackson-序列化-处理 NULL教程
ActiveMQ在C#中的应用示例分析
SpringBoot项目@Async方法问题解决方案
![](https://img.aspxhome.com/file/2023/2/58872_0s.png)
递归出现栈溢出stackoverflow的问题及解决
![](https://img.aspxhome.com/file/2023/0/89570_0s.png)
简单了解Spring beanfactory循环依赖命名重复属性
![](https://img.aspxhome.com/file/2023/3/75013_0s.png)
Unity实现俄罗斯方块(三)
Android触摸及手势操作GestureDetector
![](https://img.aspxhome.com/file/2023/9/88709_0s.png)
java实现求两个字符串最长公共子串的方法
mybatis注解与xml常用语句汇总
Java的后台文件夹下文件的遍历完整代码
![](https://img.aspxhome.com/file/2023/8/84298_0s.jpg)
详解java基于MyBatis使用示例
![](https://img.aspxhome.com/file/2023/0/59060_0s.png)