ThreadLocal的内存泄露

ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.
关于的 ThreadLocal 更多内容,请参考《 ThreadLocal 》。
在阅读了 ThreadLocal 的源码后,我发现如果我们使用不恰当,可能造成内存泄露。 经我测试,内存泄露的确存在。 虽然该内存泄露,理论上上已经不算严重。
测试代码如下
ThreadLocalTest文件
package com.teleca.robin;

public class   ThreadLocalTest  {
public   ThreadLocalTest()
{
}
ThreadLocal<Content> tl=new ThreadLocal<Content> ();
void   start()
{
System.out.println("begin");
Content content=tl.get();
if (content==null)
{
content= new Content();
tl.set(content);
}
System.out.println("try to release content data");
/ /tl.set(null) ; //@1
// tl.remove() ; //@2
tl=null; //@3
content=null ;//@4
System.out.println("request gc");
System.gc();
try  {
Thread.sleep(1000);
catch  (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("end");
}
}
class  Content
{
byte data[]=new byte[1024*1024*10];
protected void  finalize()
{
System.out.println("I am released");
}
}
运行结果
begin
try to release content data
request gc
end
注意我们尝试在 @3@4处,释放对 tlcontent的引用,以便JAVA虚拟机回收 content。但是测试结果表明还有对content的引用,以致它没有能被JAVA虚拟机回收。
我们必须把 @1@2处的代码打开,才能把让它让JAVA虚拟机回收 content.推荐打开 @2而不是 @1
@1@2处的代码打开后的运行结果如下:
begin
try to release content data
request gc
I am released
end
另外注意, @3其实并不影响运行结果。

事实上每个 Thread 实例都有一个 ThreadLocalMap 成员变量,它以 ThreadLocal 对象为 key ,以ThreadLocal绑定的对象为 Value
.我们调用ThreadLocal的 set() 方法,只是把要绑定的对象存放在当前线程的ThreadLocalMap成员变量中,以便下次通过get()方法取得它。
ThreadLocalMap 和普通map的最大区别就是它的Entry是针对 ThreadLocal 弱引用 的,即当ThreadLocal没有其他引用为空时,JVM就可以GC回收ThreadLocal,从而得到一个null的key。
关于ThreadLocalMap的更多内容请参考《 为ThreadLocal定制的ThreadLocalMap

ThreadlocalMap 维护了 ThreadLocal 对象和其绑定对象之间的关系,这个ThreadLocalMap有threshold,当超过threshold时,
ThreadLocalMap会首先检查内部ThreadLocal引用(前文说过,ThreadLocal是弱引用可以释放 )是否为null,如果存在null,那么把绑定对象的引用设置为null,以便释放 ThreadLocal 绑定的对象,这样就腾出了位置给新的ThreadLocal。如果不存在slate threadlocal,那么double threshold。
除此之外,还有两个机会释放掉已经废弃的ThreadLocal绑定的对象所占用的内存,
一、当hash算法得到的table index刚好是一个 null  的key的threadlocal时,直接用新的ThreadLocal替换掉已经废弃的。
二、每次在T hreadLocalMap 中存放ThreadLocal,hash算法没有命中既有Entry,需要新建一个Entry时,也调用 cleanSomeSlots来 遍历清理Entry数组中已经废弃的 ThreadLocal 绑定的对象的引用
此外, Thread 本身销毁时,这个ThreadLocalMap也一定被销毁了(ThreadLocalMap是Thread对象的成员),
这样所有绑定到该线程的ThreadLocal的Object Value对象,如果在外部没被引用的话(通常是这样),也就没有任何引用继续保持,所以也就被销毁回收了。

从上可以看出Java已经充分考虑了时间和空间的权衡,但是因为置为null的ThreadLocal对应的Object Value在无外部引用时,任然无法及时回收。
ThreadLocalMap 只有到达threshold时或添加entry时才做检查,不似gc是定时检查,
不过我们可以手工通过ThreadLocal的 remove() 方法或 set(null) 解除ThreadLocalMap对ThreadLocal绑定对象的引用,及时的清理废弃的threadlocal绑定对象的内存以。 remove() 往往还能做更多的清理工作,因此推荐使用它,而不使用set(null).
需要说明的是,只要不往不用的threadlocal中放入大量数据,问题不大,毕竟还有回收的机制。

被废弃了的ThreadLocal所绑定对象的引用,会在以下4情况被清理。
如果此时外部没有绑定对象的引用,则该绑定对象就能被回收了:
1 Thread结束时。
2 当Thread的 ThreadLocalMap 的threshold超过最大值时。
3 向Thread的 ThreadLocalMap 中存放一个ThreadLocal,hash算法没有命中既有Entry,而需要新建一个Entry时。
4 手工通过 ThreadLocal remove() 方法或 set(null)。

因此如果我们粗暴的把ThreadLocal设置null,而不调用 remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值