`
yangyi
  • 浏览: 115709 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

关于ThreadLocal的内存泄露

阅读更多
ThreadLocal是一种confinement,confinement和local及immutable都是线程安全的(如果JVM可信的话)。因为对每个线程和value之间存在hash表,而线程数量未知,从表象来看ThreadLocal会存在内存泄露,读了代码,发现实际上也可能会内存泄露。

事实上每个Thread实例都具备一个ThreadLocal的map,以ThreadLocal Instance为key,以绑定的Object为Value。而这个map不是普通的map,它是在ThreadLocal中定义的,它和普通map的最大区别就是它的Entry是针对ThreadLocal弱引用的,即当外部ThreadLocal引用为空时,map就可以把ThreadLocal交给GC回收,从而得到一个null的key。

这个threadlocal内部的map在Thread实例内部维护了ThreadLocal Instance和bind value之间的关系,这个map有threshold,当超过threshold时,map会首先检查内部的ThreadLocal(前文说过,map是弱引用可以释放)是否为null,如果存在null,那么释放引用给gc,这样保留了位置给新的线程。如果不存在slate threadlocal,那么double threshold。除此之外,还有两个机会释放掉已经废弃的threadlocal占用的内存,一是当hash算法得到的table index刚好是一个null key的threadlocal时,直接用新的threadlocal替换掉已经废弃的。另外每次在map中新建一个entry时(即没有和用过的或未清理的entry命中时),会调用cleanSomeSlots来遍历清理空间。此外,当Thread本身销毁时,这个map也一定被销毁了(map在Thread之内),这样内部所有绑定到该线程的ThreadLocal的Object Value因为没有引用继续保持,所以被销毁。

从上可以看出Java已经充分考虑了时间和空间的权衡,但是因为置为null的threadlocal对应的Object Value无法及时回收。map只有到达threshold时或添加entry时才做检查,不似gc是定时检查,不过我们可以手工轮询检查,显式调用map的remove方法,及时的清理废弃的threadlocal内存。需要说明的是,只要不往不用的threadlocal中放入大量数据,问题不大,毕竟还有回收的机制。

综上,废弃threadlocal占用的内存会在3中情况下清理:
1 thread结束,那么与之相关的threadlocal value会被清理
2 GC后,thread.threadlocals(map) threshold超过最大值时,会清理
3 GC后,thread.threadlocals(map) 添加新的Entry时,hash算法没有命中既有Entry时,会清理
那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。
分享到:
评论
29 楼 honey_fansy 2014-12-03  
能讲讲为什么要用final修饰吗?还是说不一定?我看了好几段代码都加了
28 楼 heipacker 2014-07-24  
czpsailer 写道
为啥这么多人投隐藏,如果LZ那里说的不对可以指出,让大家学习下呀。我觉得至少LZ精神可嘉的。

建议隐藏
27 楼 yangyi 2014-05-15  
jackyin5918 写道


个人认为, 内部的ThreadLocalMap 还是 WeakHashMap ,当WeakHashMap的key为只有 弱引用(即,只有WeakHashMap 对象本身引用该key)时,包含该key的条目会被GC,这个没有问题. 但是内存泄漏问题,是因为ThreadLocal 对象被GC掉了,变成null了(假如ThreadLocal 对象 没有设置为Static的话),因为ThreadLocalMap 的是把ThreadLocalMap 当成key的,当key为null后,对应的value无法被回收,导致内存泄漏.
这也是 ThreadLocal 对象通常需要设置为Static的原因吧.
我之前说key为null说错了,其实应该是 只有弱引用指向 这个key
这是我的个人理解,如果不当,恳请指正.

No they both use the WeakRef concept but have different implementations. And this is on purpose for some perf reasons. Check the discussion bellow for more info:
https://community.oracle.com/thread/1143681?tstart=0
26 楼 jackyin5918 2014-05-14  
yangyi 写道
jackyin5918 写道
C_J 写道
- -! 你的意思是说:ThradLocal的内部类ThreadLocalMap这个map下的Entry在某些情况下存在内存泄漏吗?

  但这个Entry是弱引用,应该是被GC启动时无条件回收吧?而弱引用并不被JVM"标记",每次GC都回收的呀,还是我理解有误?


看了楼主的文章,也有类似的问题.

2 GC后,thread.threadlocals(map) threshold超过最大值时,会清理

关于这点有个疑问,因为 WeakHashMap 在key 为null时,只要GC,就会回收
key为null的entry. 为什么这里要等到 "threshold超过最大值时" 才 "会清理"
呢?


You are talking about WeakHashMap which is as what you described, but here we are saying an internal defined Map: ThreadLocalMap which uses a Weak ref instead of extends a WeakHashMap. Essentially just the ThreadLocal object here is Weak GC collectible, so that means another direct ref, the value, still holds after the time GC happens. Please correct me if not right.


个人认为, 内部的ThreadLocalMap 还是 WeakHashMap ,当WeakHashMap的key为只有 弱引用(即,只有WeakHashMap 对象本身引用该key)时,包含该key的条目会被GC,这个没有问题. 但是内存泄漏问题,是因为ThreadLocal 对象被GC掉了,变成null了(假如ThreadLocal 对象 没有设置为Static的话),因为ThreadLocalMap 的是把ThreadLocalMap 当成key的,当key为null后,对应的value无法被回收,导致内存泄漏.
这也是 ThreadLocal 对象通常需要设置为Static的原因吧.
我之前说key为null说错了,其实应该是 只有弱引用指向 这个key
这是我的个人理解,如果不当,恳请指正.
25 楼 yangyi 2014-05-14  
jackyin5918 写道
C_J 写道
- -! 你的意思是说:ThradLocal的内部类ThreadLocalMap这个map下的Entry在某些情况下存在内存泄漏吗?

  但这个Entry是弱引用,应该是被GC启动时无条件回收吧?而弱引用并不被JVM"标记",每次GC都回收的呀,还是我理解有误?


看了楼主的文章,也有类似的问题.

2 GC后,thread.threadlocals(map) threshold超过最大值时,会清理

关于这点有个疑问,因为 WeakHashMap 在key 为null时,只要GC,就会回收
key为null的entry. 为什么这里要等到 "threshold超过最大值时" 才 "会清理"
呢?


You are talking about WeakHashMap which is as what you described, but here we are saying an internal defined Map: ThreadLocalMap which uses a Weak ref instead of extends a WeakHashMap. Essentially just the ThreadLocal object here is Weak GC collectible, so that means another direct ref, the value, still holds after the time GC happens. Please correct me if not right.
24 楼 jackyin5918 2014-05-14  
C_J 写道
- -! 你的意思是说:ThradLocal的内部类ThreadLocalMap这个map下的Entry在某些情况下存在内存泄漏吗?

  但这个Entry是弱引用,应该是被GC启动时无条件回收吧?而弱引用并不被JVM"标记",每次GC都回收的呀,还是我理解有误?


看了楼主的文章,也有类似的问题.

2 GC后,thread.threadlocals(map) threshold超过最大值时,会清理

关于这点有个疑问,因为 WeakHashMap 在key 为null时,只要GC,就会回收
key为null的entry. 为什么这里要等到 "threshold超过最大值时" 才 "会清理"
呢?
23 楼 qianhd 2012-09-30  
如果ThreadLocalMap.Entry类覆写clear方法, 把value也置为null的话,
那这种多threadLocal+线程池所产生的内存泄漏方法就应该可以避免吧.

当然多个threadLocal,而且还不停的构造, 释放的设计就是有问题的.
22 楼 yangyi 2011-05-06  
引用
Technoboy 写道
引用

那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。

我对这句话一直没太明白,什么情况下,Thread会长时间不结束呢?存在大量废弃的ThreadLocal,没有被回收,就与GC有关了。

这个是应用相关的和GC没有关系,比如线程池中的线程,core size范围内
引用

Entry是针对ThreadLocal弱引用的

弱引用的话,JVM在执行内存清理工作的时候,一般情况下会回收这段空间,但是也不一定。楼主的意思是说,如果存在内存的泄露,也是跟JVM有关系是吧?
那么,这个问题就与JVM采用什么类型的回收算法,什么时候进行回收不可达对象内存空间有关了。

和GC没有关系,是一种设计上的取舍
21 楼 Technoboy 2011-05-06  
引用

那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。

我对这句话一直没太明白,什么情况下,Thread会长时间不结束呢?存在大量废弃的ThreadLocal,没有被回收,就与GC有关了。

引用

Entry是针对ThreadLocal弱引用的

弱引用的话,JVM在执行内存清理工作的时候,一般情况下会回收这段空间,但是也不一定。楼主的意思是说,如果存在内存的泄露,也是跟JVM有关系是吧?
那么,这个问题就与JVM采用什么类型的回收算法,什么时候进行回收不可达对象内存空间有关了。
20 楼 yangyi 2011-05-05  
<div class="quote_title">weifly 写道</div>
<div class="quote_div">
<div class="quote_title">yangyi 写道</div>
<div class="quote_div">那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal</div>
<p><br><br>存在大量废弃的ThreadLocal很有可能说明程序设计有问题吧?ThreadLocal一般是static的,只要类不被回收,那ThreadLocal也不会回收的啊,也就不存在废弃的说法了。<br><br>在finally中清空ThreadLocal中持有的对象引用就不会造成内存泄露</p>
<p> </p>
<pre name="code" class="java">public class MyClass {

private static final ThreadLocal threadLocal = new ThreadLocal();

private Object value;

public void myMethod() {
try {
threadLocal.set(value);
// 业务逻辑
} finally {
threadLocal.set(null);
}
}
}
</pre>
 
<p> </p>
</div>
<p>是的,这是一个怎么使用的问题,本文只是针对源码的分析,像ls说的这种使用方法是没有问题的,因为你显式的清空了threadlocal的值,文中说明的是自动回收</p>
19 楼 weifly 2011-05-05  
<div class="quote_title">yangyi 写道</div>
<div class="quote_div">那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal</div>
<p><br><br>存在大量废弃的ThreadLocal很有可能说明程序设计有问题吧?ThreadLocal一般是static的,只要类不被回收,那ThreadLocal也不会回收的啊,也就不存在废弃的说法了。<br><br>在finally中清空ThreadLocal中持有的对象引用就不会造成内存泄露</p>
<p> </p>
<pre name="code" class="java">public class MyClass {

private static final ThreadLocal threadLocal = new ThreadLocal();

private Object value;

public void myMethod() {
try {
threadLocal.set(value);
// 业务逻辑
} finally {
threadLocal.set(null);
}
}
}
</pre>
 
<p> </p>
18 楼 yangyi 2011-05-05  
redhat 写道
yangyi 写道
redhat 写道
引用
那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。


楼主,你似懂非懂呀!你的意思是,如果存在强引用(或者是弱引用),那么在回收内存时不会清理弱引用,对吧。 呵呵,但是你想想,如果存在大量强引用的对象,你还想分配内存地址给你的对象,那会报“内存不够用”的异常,请您再发表这些言论时,为了避免误导大家,务必先做个实验验证!


ThreadLocal是弱引用的,但是map本身是强引用的,所以即使key被回收为null,value还在,另外,在很多threadlocal的应用中,value对象不再调用环境之外保留强引用,所以才会引起所谓“内存泄露”

看来要给你好好上一课了,map里的value,如果使用强引用,那么持续的往里面添加value,即使你外部程序对此value释放引用了,但是,你的map的key还没有,这样的map随着时间膨胀,不remove一些值的话,就会引起内存不够,这才是weakreferencemap使用的原因,当你程序的强引用指向此value断掉,那么应该map里自动断掉引用,回收此value,不管是weakmap还是softmap,都应该是对key的softreference或者weakrefrence的包装,试想下,如果程序运行过程中,你的value对象被回收了,那是多么悲哀的事情,程序不都是报nullpointexception了。
要知其然,才能明白所以然。

你说的都是对的,你仔细看我说的,和你说的矛盾吗?你为什么觉得要给我上一课,你懂的,别人不懂?
17 楼 redhat 2011-05-05  
yangyi 写道
redhat 写道
引用
那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。


楼主,你似懂非懂呀!你的意思是,如果存在强引用(或者是弱引用),那么在回收内存时不会清理弱引用,对吧。 呵呵,但是你想想,如果存在大量强引用的对象,你还想分配内存地址给你的对象,那会报“内存不够用”的异常,请您再发表这些言论时,为了避免误导大家,务必先做个实验验证!


ThreadLocal是弱引用的,但是map本身是强引用的,所以即使key被回收为null,value还在,另外,在很多threadlocal的应用中,value对象不再调用环境之外保留强引用,所以才会引起所谓“内存泄露”

看来要给你好好上一课了,map里的value,如果使用强引用,那么持续的往里面添加value,即使你外部程序对此value释放引用了,但是,你的map的key还没有,这样的map随着时间膨胀,不remove一些值的话,就会引起内存不够,这才是weakreferencemap使用的原因,当你程序的强引用指向此value断掉,那么应该map里自动断掉引用,回收此value,不管是weakmap还是softmap,都应该是对key的softreference或者weakrefrence的包装,试想下,如果程序运行过程中,你的value对象被回收了,那是多么悲哀的事情,程序不都是报nullpointexception了。
要知其然,才能明白所以然。
16 楼 yangyi 2011-05-05  
redhat 写道
引用
那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。


楼主,你似懂非懂呀!你的意思是,如果存在强引用(或者是弱引用),那么在回收内存时不会清理弱引用,对吧。 呵呵,但是你想想,如果存在大量强引用的对象,你还想分配内存地址给你的对象,那会报“内存不够用”的异常,请您再发表这些言论时,为了避免误导大家,务必先做个实验验证!


ThreadLocal是弱引用的,但是map本身是强引用的,所以即使key被回收为null,value还在,另外,在很多threadlocal的应用中,value对象不再调用环境之外保留强引用,所以才会引起所谓“内存泄露”
15 楼 redhat 2011-05-04  
末了,为了说明白一点,再补充上次回复说一句: 报“内存不够”的异常,一般是您设计的问题,为什么要设计很多线程不停地跑,而且不等它们跑完就要加新的线程进来占用内存?这和你有能容纳1T个对象能力的内存,你创建1T个对象,这些对象都有强引用指向,所以都不能回收,你还要创建1T个对象,那都会报内存不够的异常。
14 楼 redhat 2011-05-04  
引用
那么何时会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又不再添加新的ThreadLocal(或新添加的ThreadLocal恰好和一个废弃ThreadLocal在map中命中)时。


楼主,你似懂非懂呀!你的意思是,如果存在强引用(或者是弱引用),那么在回收内存时不会清理弱引用,对吧。 呵呵,但是你想想,如果存在大量强引用的对象,你还想分配内存地址给你的对象,那会报“内存不够用”的异常,请您再发表这些言论时,为了避免误导大家,务必先做个实验验证!
13 楼 shellfj 2010-07-06  
欣赏lz看代码的精神和效果。
12 楼 beneo 2010-07-06  
threadlocal是一个weakhashmap

那么,threadlocal<you_instance>的you_instance又引用了threadlocal的话,会出现内存泄漏。

线程池的话,只会出现问题,线程不死,threadlocal里面的值不会被回收,存在一些风险,不会带来内存泄漏。
11 楼 rain2005 2010-07-05  
看来没有几个真正搞清楚的,请看源代码吧。

ThreadLocal一定要用static类型的,否则内存泄露。
10 楼 zhxing 2010-07-05  
Spring 中用到了大量的ThreadLocal,也未见什么大问题,是否lz 多虑了。

另外个人一个小小见解:
一般容器会有个线程池之类的,可以说是固定了线程数量了,线程被重用了,ThreadLocal 的值也会被覆盖,这时gc就可以清理了。。

相关推荐

    ThreadLocal 内存泄露的实例分析1

    但这里,`ThreadLocal` 的设计使得其内部的引用(即使是最弱的引用)在类加载器试图卸载时依然存在,这就形成了一个内存泄漏点。 解决这个问题的关键在于,我们需要确保在不再需要 `ThreadLocal` 变量时,及时清除...

    ThreadLocal内存泄露分析

    【标题】:“ThreadLocal内存泄露分析” 在Java编程中,ThreadLocal是一个强大的工具类,它为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。然而,...

    ThreadLocal中内存泄漏和数据丢失问题的问题浅析及解决方案.docx

    ThreadLocal 中内存泄漏和数据丢失问题的问题浅析及解决方案 ThreadLocal 是 Java 中的一种线程本地存储机制,它可以解决线程之间的数据传递问题。然而,在使用 ThreadLocal 时,可能会出现内存泄漏和数据丢失问题...

    ThreadLocal内存泄漏思维导图完整版

    内存泄漏就是JVM垃圾回收器对某个对象占据的内存在较长时间内一直没法回收,没法回收的原因并不是因为垃圾回收器有bug,而是由于对象没法判定为垃圾(但实际上该对象已经是不会被使用了)。这里说的“较长时间”是一...

    04、导致JVM内存泄露的ThreadLocal详解-ev

    04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、...

    ThreadLocal原理及内存泄漏原因

    让我们深入理解ThreadLocal的工作原理以及可能导致内存泄漏的原因。 首先,ThreadLocal是如何实现每个线程都有独立变量副本的呢?这主要得益于内部类`ThreadLocalMap`。当一个ThreadLocal对象被创建后,它并没有...

    实例详解Java中ThreadLocal内存泄露

    然而,如果不正确地使用ThreadLocal,可能会导致内存泄露,尤其是在Java EE容器如Tomcat中。本文将深入探讨这个问题,并提供解决方案。 在问题背景部分,我们看到一个示例,其中`LeakingServlet`类内部使用了一个...

    2、导致JVM内存泄露的ThreadLocal详解

    ### 导致JVM内存泄露的ThreadLocal详解 #### 一、为什么要有ThreadLocal 在多线程编程中,为了避免线程间的数据竞争和保证线程安全性,常常需要使用同步机制如`synchronized`来控制线程对共享资源的访问。然而,...

    关于java内存泄漏

    ### 关于Java内存泄漏 #### 一、Java内存管理机制 Java的一大特色在于其自动化的内存管理机制,这主要依赖于垃圾收集器(Garbage Collection, GC)来自动完成对象的内存分配与回收。尽管这一特性极大地减轻了...

    ThreadLocal应用示例及理解

    当线程结束时,与其关联的ThreadLocal变量不会自动清除,可能会导致内存泄漏。因此,推荐在不再使用ThreadLocal时显式调用`remove()`方法。 ```java threadLocal.remove(); ``` ### 示例:线程安全的计数器 假设...

    ThreadLocal

    - 内存泄漏:如果线程长时间存活,或者ThreadLocal对象没有被正确清理,可能导致ThreadLocalMap中的引用无法被垃圾回收,从而造成内存泄漏。 - 不适用于跨线程通信:ThreadLocal只保证同一线程内的数据隔离,不同...

    Java中ThreadLocal的设计与使用

    ### ThreadLocal内存泄漏问题 由于ThreadLocal变量是存储在线程的ThreadLocalMap中,如果线程长时间运行并且不清理ThreadLocal,当ThreadLocal对象被垃圾收集时,其在ThreadLocalMap中的引用将变为"幽灵引用"(弱...

    java中ThreadLocal详解

    需要注意的是,尽管使用了弱引用来避免内存泄漏,但仍需谨慎管理`ThreadLocal`实例的生命周期,确保及时释放不再使用的资源。此外,`ThreadLocalMap`通过开放地址法来解决哈希冲突,进一步提高了性能并减少了内存...

    JVM的基础和调优【JMM 内存结构 GC OOM 性能调优 ThreadLocal】

    内存泄露:是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄露, 一次的内存泄露似乎不会有大的影响,但是内存泄露堆积的后果就是内存溢出 JMM 决定一个线程对共享变量的写入何时对另一个线程可见,...

    ThreadLocal_ThreadLocal源码分析_

    Entry继承自WeakReference&lt;ThreadLocal&gt;,这意味着ThreadLocal对象如果不再被引用,可以被垃圾收集器回收,避免内存泄漏。然而,这种设计也存在一个问题:如果ThreadLocal没有被外部引用,但其对应的Entry还在Map中...

    threadLocal

    3. 内存管理:了解Java的内存模型和垃圾回收机制,才能理解ThreadLocal的内存泄漏风险和弱引用的作用。 4. HTTP相关:虽然题目中没有直接涉及,但HTTPClient是一个常见的网络通信工具,经常和ThreadLocal结合使用,...

    ThreadLocal原理及在多层架构中的应用

    - **内存泄漏风险**:如果不正确地使用ThreadLocal,如忘记清理ThreadLocal变量,可能导致内存泄漏。 - **线程隔离性**:ThreadLocal只在创建它的线程内有效,无法跨线程共享数据。 - **难以调试**:由于ThreadLocal...

    ThreadLocal整理.docx

    ThreadLocal 整理 ThreadLocal 是 Java 中... ThreadLocal 是 Java 中的一个重要组件,它能够在每个线程中保持独立的副本,解决 Hash 冲突的机制是通过斐波那契数来实现的,并且提供了扩容机制来避免内存泄漏的问题。

    Java系统中内存泄漏测试方法的研究

    4. ThreadLocal:如果线程局部变量未被正确清理,可能会导致内存泄漏。 5. 注册监听器未取消:注册的监听器如果没有在适当的时候解除注册,会持续持有对对象的引用。 四、内存泄漏的检测方法 1. **手动代码审查**...

Global site tag (gtag.js) - Google Analytics