• 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处,释放对tl和content的引用,以便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替换掉已经废弃的。
    二、每次在ThreadLocalMap中存放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绑定的对象长期也能被回收,因而产出内存泄露。
     
    http://blog.csdn.net/hudashi/article/details/7076880
     
     
  • 相关阅读:
    shell中的交互模式:expect
    hive(II)--sql考查的高频问题
    ETL工具--kettle篇(17.10.09更新)
    hive(I)--学习总结之常用技能
    ubantu上搭建hive环境
    shell实例练习+详解
    搭建hadoop、hdfs环境--ubuntu(完全分布式)
    oracle 获取一个字段的年月日
    oracle 两表更新 报错ORA-01779: 无法修改与非键值保存表对应的列
    oracle 查看表空间 添加数据文件
  • 原文地址:https://www.cnblogs.com/softidea/p/4819836.html
Copyright © 2020-2023  润新知