• 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绑定的对象长期也能被回收,因而产出内存泄露。

  • 相关阅读:
    优化网站设计(四):对资源启用压缩
    优化网站设计(三):对资源添加缓存控制
    旧貌换新颜 华为助力甲壳虫科技打造智慧环卫
    软件开发项目云端All-In-One体验
    码农进“城”之路---我从机械男转入软件开发行业的亲身经历
    如何选择版本控制系统之三---代码托管操作
    推荐五款Android 应用的自动化测试工具
    移动应用/APP的测试流程及方法
    老程序员总结的16条经验教训
    30多个Android 开发者工具 带你开发带你飞
  • 原文地址:https://www.cnblogs.com/firstdream/p/7886779.html
Copyright © 2020-2023  润新知