精华内容
下载资源
问答
  • ThreadLocal内存泄露

    2018-11-10 22:04:06
    关于ThreadLocal内存泄露的备忘 ThreadLocal从名字上来说就很好理解,就是用于线程(Thread)私有(Local)的存储结构,这种结构能够使得线程能够使用只有自己能够访问和修改的变量,从而实现多个线程之间的资源互相...

    关于ThreadLocal内存泄露的备忘

    ThreadLocal从名字上来说就很好理解,就是用于线程(Thread)私有(Local)的存储结构,这种结构能够使得线程能够使用只有自己能够访问和修改的变量,从而实现多个线程之间的资源互相隔离,达到安全并发的目的。

    也因此,ThreadLocal作为线程并发中的一种资源使用方式,得到了很广泛的应用,比如Spring MVC、Hibernate等。
    不过值得一提的是,通常有人会讲ThreadLocal和synchronised等放在一起,作为形成安全并发的手段之一。其实我觉得这是比较容易使人误导的,因为两者的目的性完全不一样。
    ThreadLocal主要的是用于独享自己的变量,避免一些资源的争夺,从而实现了空间换时间的思想。
    而synchronised则主要用于临界(冲突)资源的分配,从而能够实现线程间信息同步,公共资源共享等,所以严格来说synchronised其实是能够实现ThreadLocal所需要的达到的效果的,只不过这样会带来资源争夺导致并发性能下降,而且还有synchronised、线程切换等一些可能不必要的开销。

    对于ThreadLocal而言,其实使用起来有点像基础类型的装箱类型的感觉(个人觉得其实也可以算是一种装饰器模式的使用?),具体的使用就不在啰嗦了。下面就看看这次备忘的重点,如何导致内存泄漏的。

    其实网上有的文章已经讲的听清楚的,觉得有张图特别好先引用到这里,来源于ThreadLocal可能引起的内存泄露:

    ThreadLocal可能引起的内存泄露

    image

    所以简单的说,主要原因就是在于TreadLocal中用到的自己定义的Map(和常用的Map接口不同)中,使用的Key值是一个WeakReference类型的值(弱引用会在下一次GC时马上释放而不管是否被引用)。那么如果这个Key在GC时被释放了,就会导致Value永远都不会被调用到,但是如果线程不结束,又一直存在。

    因为可能不熟悉这部分内容的同学(例如几周以后的我)会感觉有点迷糊为什么这个图是这样的,就具体再解释一下细节点:

    • 首先当然是看一下我们的主角ThreadLocal类,只保留了几个重点的地方,特别的是内部静态类的ThreadLocalMap是ThreadLocal自己实现的一个Map,而这个Map用使用了ThreadLocal作为了一个弱引用的Key(也就是主要问题点)。

    p.s.不知道各位第一次看的时候会不会跟我一样有种我是老子的儿子的同时又是老子的老子的感觉,哈哈哈

    public class ThreadLocal<T> {
        
        //  获取Thread里面的Map
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
        // (敲黑板)
        // 这里是重点!!!
        static class ThreadLocalMap {
            
            // 这里是凶器!!!
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
            ... 
        }
        ... 
     }
    
    • 接着不得不说的就是我们的大佬Thread类,里面关于ThreadLocal部分的内容主要是这样滴。我们可以看到这里主要是声明了ThreadLocal里面的Map作为类变量来提供给线程使用的。也正式因为如此,才会在ThreadLocal里面的getMap方法是拉取的Thread里面的Map。

    p.s. 感觉确实有点绕

    public class Thread implements Runnable {
    
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. 
         */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    • 于是到这里我们就明白了,其实每个Thread里面都有一个Map,Map里面的Key是ThreadLocal类的一个实例,之所以会比较混淆主要还是因为这里的Map又是ThreadLocal里面的一个内部静态类。

    所以到这里其实有两个问题是暂时还没想通的,也希望有各位大佬指点一二:

    • TreadLocalMap 其实是可以抽取成单独的类的?这样就使得逻辑和嵌套关系没有这么绕的感觉。
    • 为什么只有Key要设计成WeakReference而不是Key和Value都是,或者这里为什么要设置弱引用?如果为了保护内存空间其实两者都是弱引用更好吧,是不是有什么其它考虑?

    回归到内存泄露是因为WeakReference Key的问题,当然,Java的各位大佬肯定早就想到这个问题了,可以看到人家注释里面是这么说的,大意就是如果key==null的时候,就可以认为这个值无效了,可以调用expunged进行清理:

    /**
      * The entries in this hash map extend WeakReference, using
      * its main ref field as the key (which is always a
      * ThreadLocal object).  Note that null keys (i.e. entry.get()
      * == null) mean that the key is no longer referenced, so the
      * entry can be expunged from table.  Such entries are referred to
      * as "stale entries" in the code that follows.
      */
    

    而这个expungeStaleEntry方法在get、set时都会有间接的调用,而且remove方法中也会显示的调用,这也就是为什么有的文章中说通过在线程调用完成之后,通过调用remove方法能有效的杜绝该泄露问题的原因。

    当然简单来说理解到这里就基本明了内存泄露的原因,但是其实再深入一点来说,如果泄露的原因是Key被释放,而Value没有释放,那么是否一定会有泄露呢?
    答案当然是否定的,因为如果是一般的线程场景中,除了会调用expungeStaleEntry来进行清理,最差,在线程结束之时,自然也就消除了引用从而使得Value得以GC回收。

    所以,会不会有线程一直不结束的场景呢?
    当然答案是肯定的,最简单来说线程只要一直在wait就不会结束了,不过这种场景下其实和泄露也没啥关系的感觉。
    其实最常用的线程一直不结束的场景,自然就是线程池了。因为这种情况下,线程是一直在不断的重复运行的,从而也就造成了value可能造成累积的情况。具体的模拟可以参考: 深入理解ThreadLocal的"内存溢出"

    最后来做个总结吧,可能泄露的场景仅且仅在:

    • 线程run方法结束后没有显示的调用remove进行清理
    • 线程在线程池的模式下,一直重复运行

    转自

    展开全文
  • ThreadLocal 内存泄露

    2021-02-10 11:44:34
    ThreadLocal 定义,以及是否可能引起的内存泄露(threadlocalMap的Key是弱引用,用线程池有可能泄露) ThreadLocal 也可以跟踪一个请求,从接收请求,处理请求,到返回请求,只要线程不销毁,就可以在线程的任何地方...

    ThreadLocal 定义,以及是否可能引起的内存泄露(threadlocalMap的Key是弱引用,用线程池有可能泄露)

    ThreadLocal 也可以跟踪一个请求,从接收请求,处理请求,到返回请求,只要线程不销毁,就可以在线程的任何地方,调用这个参数,这是百度二面的题目,参考:

    Threadlocal 传递参数(百度二面)

    总结:

    1. JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
    2. JVM利用调用remove、get、set方法的时候,回收弱引用。
    3. 当ThreadLocal存储很多Key为null的Entry的时候,而不再去调用remove、get、set方法,那么将导致内存泄漏。
    4. 当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。http://www.importnew.com/22039.html

     

    那么如何有效的避免呢?

    事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

    threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.

      在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用.

      

      每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.

      所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。  

      PS.Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。 

    应用场景

    最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。如

    复制代码

    private static ThreadLocal < Connection > connectionHolder = new ThreadLocal < Connection > () {
        public Connection initialValue() {
            return DriverManager.getConnection(DB_URL);
        }
    };
    
    public static Connection getConnection() {
        return connectionHolder.get();
    }
    private static final ThreadLocal threadSession = new ThreadLocal();
    
    public static Session getSession() throws InfrastructureException {
        Session s = (Session) threadSession.get();
        try {
            if (s == null) {
                s = getSessionFactory().openSession();
                threadSession.set(s);
            }
        } catch (HibernateException ex) {
            throw new InfrastructureException(ex);
        }
        return s;
    } 

    复制代码

    一、目录

         1、ThreadLocal是什么?有什么用?

         2、ThreadLocal源码简要总结?

         3、ThreadLocal为什么会导致内存泄漏?

    二、ThreadLocal是什么?有什么用?

    引入话题:在并发条件下,如何正确获得共享数据?举例:假设有多个用户需要获取用户信息,一个线程对应一个用户。在mybatis中,session用于操作数据库,那么设置、获取操作分别是session.set()、session.get(),如何保证每个线程都能正确操作达到想要的结果?

    复制代码

    /**
     * 回顾synchronized在多线程共享线程的问题
     * @author qiuyongAaron
     */
    public class ThreadLocalOne {
         volatile Person person=new Person();
     
         public  synchronized String setAndGet(String name){
              //System.out.print(Thread.currentThread().getName()+":");
               person.name=name;
               //模拟网络延迟
               try {
                    TimeUnit.SECONDS.sleep(2);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
               return person.name;
         }
     
         public static void main(String[] args) {
               ThreadLocalOne  threadLocal=new ThreadLocalOne();
               new Thread(()->System.out.println(threadLocal.setAndGet("arron")),"t1").start();
               new Thread(()->System.out.println(threadLocal.setAndGet("tony")),"t2").start();
         }
    }
     
    class Person{
         String name="tom";
         public Person(String name) {
               this.name=name;
         }
     
         public Person(){}
    }
     
    运行结果:
    无synchronized:
    t1:tony
    t2:tony
     
    有synchronized:
    t1:arron
    t2:tony

    复制代码

     

    步骤分析:

    1. 无synchronized的时候,因为非原子操作,显然不是预想结果,可参考我关于synchronized的讨论。
    2. 现在,我们的需求是:每个线程独立的设置获取person信息,不被线程打扰。
    3. 因为,person是共享数据,用同步互斥锁synchronized,当一个线程访问共享数据的时候,其他线程堵塞,不再多余赘述。

     

    通过举例问题,可能大家又会很疑惑?

    mybatis、hibernate是如何实现的呢?

    synchronized不会很消耗资源,当成千上万个操作的时候,承受并发不说,数据返回延迟如何确保用户体验?

     

    ThreadLocal是什么?有什么用?

    复制代码

    /**
     * 谈谈ThreadLocal的作用
     * @author qiuyongAaron
     */
    public class ThreadLocalThree {
         ThreadLocal<Person> threadLocal=new ThreadLocal<Person>();
         public String setAndGet(String name){
               threadLocal.set(new Person(name));
               try {
                    TimeUnit.SECONDS.sleep(2);
               } catch (InterruptedException e) {
                    e.printStackTrace();
               }
               return threadLocal.get().name;
         }
     
         public static void main(String[] args) {
               ThreadLocalThree  threadLocal=new ThreadLocalThree();
               new Thread(()->System.out.println("t1:"+threadLocal.setAndGet("arron")),"t1").start();
               new Thread(()->System.out.println("t2:"+threadLocal.setAndGet("tony")),"t2").start();
         }
    }
    运行结果:
    t1:arron
    t2:tony

    复制代码

     

     

    分析:

    1、根据预期结果,那ThreadLocal到底是什么?

    回顾Java内存模型:

      

          在虚拟机中,堆内存用于存储共享数据(实例对象),堆内存也就是这里说的主内存。

         每个线程将会在堆内存中开辟一块空间叫做线程的工作内存,附带一块缓存区用于存储共享数据副本。那么,共享数据在堆内存当中,线程通信就是通过主内存为中介,线程在本地内存读并且操作完共享变量操作完毕以后,把值写入主内存。

    1. ThreadLocal被称为线程局部变量,说白了,他就是线程工作内存的一小块内存,用于存储数据。
    2. 那么,ThreadLocal.set()、ThreadLocal.get()方法,就相当于把数据存储于线程本地,取也是在本地内存读取就不会像synchronized需要频繁的修改主内存的数据,再把数据复制到工作内存,也大大提高访问效率

     

    2、ThreadLocal到底有什么用?

    1. 回到最开始的举例,也就等价于mabatis、hibernate为什么要使用threadlocal来存储session?
    2. 作用一:因为线程间的数据交互是通过工作内存与主存的频繁读写完成通信,然而存储于线程本地内存,提高访问效率,避免线程阻塞造成cpu吞吐率下降
    3. 作用二:在多线程中,每一个线程都需要维护session,轻易完成对线程独享资源的操作

     

    总结:

         Threadlocal是什么?在堆内存中,每个线程对应一块工作内存,threadlocal就是工作内存的一小块内存。

         Threadlocal有什么用?threadlocal用于存取线程独享数据,提高访问效率。

    三、ThreadLocal源码简要总结?

    那有同学可能还是有点云里雾里,感觉还是没有吃透?那线程内部如何去保证线程独享数据呢?

     

    在这里,我只做简要总结,若有兴趣,可参考文章尾部的文章链接。重点看get、set方法。

    复制代码

     public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
     }

    复制代码

     

    分析:

    1. 一个线程对应一个ThreadLocalMap ,可以存储多个ThreadLocal对象。
    2. ThreadLocal对象作为key、独享数据作为value。
    3. ThreadLocalMap可参考HashMap,在ThreadMap里面存在Entry数组也就是一个Entry一个键值对。

    复制代码

    public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

    复制代码

     

    分析:

    1. 一个线程对应一个ThreadLocalMap,get()就是当前线程获取自己的ThreadLocalMap。
    2. 线程根据使用那一小块的threadlocal,根据ThreadLocal对象作为key,去获取存储于ThreadLocalMap中的值。

     

    总结:

         回顾一下,我们在单线程中如何使用HashMap的?hashMap根据数组+链表来实现HashMap,一个key对应一个value。那么,我们抽象一下,Threadlocal也相当于在多线程中的一种HashMap用法,相当于对ThradLocal的操作也就如单线程操作一样。

         总之,ThreadLocal就是堆内存的一块小内存,它用ThreadLocalMap维护ThreadLocal对象作为key,独享数据作为value的东西。

     

    四、ThreadLocal为什么会导致内存泄漏?

    synchronized是用时间换空间(牺牲时间)、ThreadLocal是用空间换时间(牺牲空间),为什么这么说?

    因为synchronized操作数据,只需要在主存存一个变量即可,就阻塞等共享变量,而ThreadLocal是每个线程都创建一块小的堆工作内存。显然,印证了上面的说法。

     

    一个线程对应一块工作内存,线程可以存储多个ThreadLocal。那么假设,开启1万个线程,每个线程创建1万个ThreadLocal,也就是每个线程维护1万个ThreadLocal小内存空间,而且当线程执行结束以后,假设这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出。

     

    怎么办?难道JVM就没有提供什么解决方案吗?

    ThreadLocal当然有想到,所以他们把ThreadLocal里的Entry设置为弱引用,当垃圾回收的时候,回收ThreadLocal。

    什么是弱引用?

    1. Key使用强引用:也就是上述说的情况,引用ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。
    2. Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除
    3. 比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除

     

    那按你这么说,既然JVM有保障了,还有什么内存泄漏可言?

    ThreadLocalMap使用ThreadLocal对象作为弱引用,当垃圾回收的时候,ThreadLocalMap中Key将会被回收,也就是将Key设置为null的Entry。如果线程迟迟无法结束,也就是ThreadLocal对象将一直不会回收,回顾到上面存在很多线程+TheradLocal,那么也将导致内存泄漏。(内存泄露的重点)

     

    其实,在ThreadLocal中,当调用remove、get、set方法的时候,会清除为null的弱引用,也就是回收ThreadLocal。

     ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。 

    ThreadLocal提供的方法

     

    ThreadLocal API

    对于ThreadLocal而言,常用的方法,就是get/set/initialValue方法。

    我们先来看一个例子

     

    demo

    运行结果

     

    是你想象中的结果么?

    很显然,在这里,并没有通过ThreadLocal达到线程隔离的机制,可是ThreadLocal不是保证线程安全的么?这是什么鬼?

    虽然,ThreadLocal让访问某个变量的线程都拥有自己的局部变量,但是如果这个局部变量都指向同一个对象呢?这个时候ThreadLocal就失效了。仔细观察下图中的代码,你会发现,threadLocal在初始化时返回的都是同一个对象a!

     

    看一看ThreadLocal源码

    我们直接看最常用的set操作:

     

    set

     

     

    线程局部变量

     

     

    createMap

     

    你会看到,set需要首先获得当前线程对象Thread;

    然后取出当前线程对象的成员变量ThreadLocalMap;

    如果ThreadLocalMap存在,那么进行KEY/VALUE设置,KEY就是ThreadLocal;

    如果ThreadLocalMap没有,那么创建一个;

    说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值。

    看一下get操作:

     

    get

    这里其实揭示了ThreadLocalMap里面的数据存储结构,从上面的代码来看,ThreadLocalMap中存放的就是Entry,Entry的KEY就是ThreadLocal,VALUE就是值。

    ThreadLocalMap.Entry:

     

    弱引用?

     

    在JAVA里面,存在强引用、弱引用、软引用、虚引用。这里主要谈一下强引用和弱引用。

    强引用,就不必说了,类似于:

    A a = new A();

    B b = new B();

    考虑这样的情况:

    C c = new C(b);

    b = null;

    考虑下GC的情况。要知道b被置为null,那么是否意味着一段时间后GC工作可以回收b所分配的内存空间呢?答案是否定的,因为即便b被置为null,但是c仍然持有对b的引用,而且还是强引用,所以GC不会回收b原先所分配的空间!既不能回收利用,又不能使用,这就造成了内存泄露

    那么如何处理呢?

    可以c = null;也可以使用弱引用!(WeakReference w = new WeakReference(b);)

    分析到这里,我们可以得到:

     

    内存结构图

    这里我们思考一个问题:ThreadLocal使用到了弱引用,是否意味着不会存在内存泄露呢? 

    首先来说,如果把ThreadLocal置为null,那么意味着Heap中的ThreadLocal实例不在有强引用指向,只有弱引用存在,因此GC是可以回收这部分空间的,也就是key是可以回收的。但是value却存在一条从Current Thread过来的强引用链。因此只有当Current Thread销毁时,value才能得到释放。

    因此,只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间内不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,比如使用线程池的时候,线程结束是不会销毁的,再次使用的,就可能出现内存泄露。

    那么如何有效的避免呢?

    事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

     

     

    展开全文
  • 在换工作的时候,每个面试官都爱问这个问题。最初的时候,自己非常不解。... /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadL...

    a9dd7ba3fe0ff4f15aaa3fac3f4e67b4.png

    在换工作的时候,每个面试官都爱问这个问题。最初的时候,自己非常不解。

    最后,自己回家调研了一下。发现了问题。

    看下面代码,是Thread 类。里面有个属性。

      /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;

    这就是为什么Thread ThreadLocal有关系的原因。

    /* * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * * * * * * * * * * * * * * * * * * * * */package java.lang;import java.lang.ref.Reference;import java.lang.ref.ReferenceQueue;import java.lang.ref.WeakReference;import java.security.AccessController;import java.security.AccessControlContext;import java.security.PrivilegedAction;import java.util.Map;import java.util.HashMap;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentMap;import java.util.concurrent.locks.LockSupport;import sun.nio.ch.Interruptible;import sun.reflect.CallerSensitive;import sun.reflect.Reflection;import sun.security.util.SecurityConstants;/** * A thread is a thread of execution in a program. The Java * Virtual Machine allows an application to have multiple threads of * execution running concurrently. *  * Every thread has a priority. Threads with higher priority are * executed in preference to threads with lower priority. Each thread * may or may not also be marked as a daemon. When code running in * some thread creates a new Thread object, the new * thread has its priority initially set equal to the priority of the * creating thread, and is a daemon thread if and only if the * creating thread is a daemon. *  * When a Java Virtual Machine starts up, there is usually a single * non-daemon thread (which typically calls the method named * main of some designated class). The Java Virtual * Machine continues to execute threads until either of the following * occurs: *  * The exit method of class Runtime has been *     called and the security manager has permitted the exit operation *     to take place. * All threads that are not daemon threads have died, either by *     returning from the call to the run method or by *     throwing an exception that propagates beyond the run *     method. *  *  * There are two ways to create a new thread of execution. One is to * declare a class to be a subclass of Thread. This * subclass should override the run method of class * Thread. An instance of the subclass can then be * allocated and started. For example, a thread that computes primes * larger than a stated value could be written as follows: * 
    * class PrimeThread extends Thread { * long minPrime; * PrimeThread(long minPrime) { * this.minPrime = minPrime; * } * * public void run() { * // compute primes larger than minPrime * . . . * } * } *
    * * The following code would then create a thread and start it running: *
    * PrimeThread p = new PrimeThread(143); * p.start(); * * * The other way to create a thread is to declare a class that * implements the Runnable interface. That class then * implements the run method. An instance of the class can * then be allocated, passed as an argument when creating * Thread, and started. The same example in this other * style looks like the following: *
    * class PrimeRun implements Runnable { * long minPrime; * PrimeRun(long minPrime) { * this.minPrime = minPrime; * } * * public void run() { * // compute primes larger than minPrime * . . . * } * } * * * The following code would then create a thread and start it running: *
    * PrimeRun p = new PrimeRun(143); * new Thread(p).start(); * * * Every thread has a name for identification purposes. More than * one thread may have the same name. If a name is not specified when * a thread is created, a new name is generated for it. * * Unless otherwise noted, passing a {@code null} argument to a constructor * or method in this class will cause a {@link NullPointerException} to be * thrown. * * @author unascribed * @see Runnable * @see Runtime#exit(int) * @see #run() * @see #stop() * @since JDK1.0 */publicclass Thread implements Runnable { /* Make sure registerNatives is the first thing does. */ private static native void registerNatives(); static { registerNatives(); } private volatile char name[]; private int priority; private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; /* Whether or not the thread is a daemon thread. */ private boolean daemon = false; /* JVM state */ private boolean stillborn = false; /* What will be run. */ private Runnable target; /* The group of this thread */ private ThreadGroup group; /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; /* The inherited AccessControlContext of this thread */ private AccessControlContext inheritedAccessControlContext; /* For autonumbering anonymous threads. */ private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; } /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;

    在看看 ThreadLocal类,有个ThreadLocalMap 。并且Entry 是继承WeakReference,也就是弱引用。这个时候 在GC的时候 会被回收。

        /**     * ThreadLocalMap is a customized hash map suitable only for     * maintaining thread local values. No operations are exported     * outside of the ThreadLocal class. The class is package private to     * allow declaration of fields in class Thread.  To help deal with     * very large and long-lived usages, the hash table entries use     * WeakReferences for keys. However, since reference queues are not     * used, stale entries are guaranteed to be removed only when     * the table starts running out of space.     */    static class ThreadLocalMap {        /**         * The entries in this hash map extend WeakReference, using         * its main ref field as the key (which is always a         * ThreadLocal object).  Note that null keys (i.e. entry.get()         * == null) mean that the key is no longer referenced, so the         * entry can be expunged from table.  Such entries are referred to         * as "stale entries" in the code that follows.         */        static class Entry extends WeakReference<ThreadLocal>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal> k, Object v) {                super(k);                value = v;            }        }

        Threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法.

     在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引用,虚线代表弱引用.

      a83ab87212c4c73b2cd0035a73a651e1.png

      每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal. 当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.

      所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。  

      Java为了最小化减少内存泄露的可能性和影响,在ThreadLocal的get,set的时候都会清除线程Map里所有key为null的value。所以最怕的情况就是,threadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。

    64e96823e6b159b00a88d18375cd5d6c.png

    版权声明:文章源于网络,不代表版主个人观点,如侵权或者不当之处,请联系编辑删除.

    投稿信箱:714343803@qq.com(欢迎您原创投稿)

    责任编辑:沈丘人(微信:ishenqiu)QQ:714343803

    ⊙网站: http://www.laomn.com

    你若喜欢,别忘了点个【】和【哦 838e9a70bf64703e8a45aa953da1f6c7.png

    展开全文
  • 一篇文章我们来分析一个Java中ThreadLocal内存泄露的案例。分析问题的过程比结果更重要,理论结合实际才能彻底分析出内存泄漏的原因。
  • ThreadLocal内存泄露什么是内存泄漏和内存溢出我们在讲ThreadLocal的内存泄漏之前,首先要搞清楚什么是内存泄漏,那要说起内存泄漏,肯定还有个概念需要说,那就是内存溢出,这两者是个啥呢?首先什么是内存泄漏:...

    ThreadLocal的内存泄露

    什么是内存泄漏和内存溢出

    我们在讲ThreadLocal的内存泄漏之前,首先要搞清楚什么是内存泄漏,那要说起内存泄漏,肯定还有个概念需要说,那就是内存溢出,这两者是个啥呢?

    首先什么是内存泄漏:

    • 说的简单点那就是因为操作不当或者一些错误导致没有能释放掉已经不再使用的内存,这就是内存泄漏,也就是说,有些内存已经不会再使用了,但是却没有给它释放掉,这就一直占用着内存空间,从而导致了内存泄漏。

    那什么是内存溢出呢?

    • 这个简单点说就是内存不够用了,我运行一个程序比如说需要50M的内存,但是现在内存就剩下20M了,那程序运行就会发生内存溢出,也就是告诉你内存不够用,这时候程序就无法运行了。

    好,了解了基本概念之后,我们再来看看T和read Local的内存泄漏,那为什么T和read Local会产生内存泄漏呢?我们再来看看这张图:

    573851a04f66f420c83ffd47a4ecd5f5.png

    经过我们上述的讨论,我们大致知道了ThreadLocal其实本质上是在每个线程中单独维护了一个ThreadLocalMap数据结构,这个ThreadLocalMap是每个线程独有的,只有根据当前线程才能找到当前线程的这个ThreadLocalMap,这就实现了线程之前的隔离。

    我们看上面那张图,每个线程根据找到自己维护的ThreadLocalMap,然后可以操作这个数据结构,往里面存取数据,而ThreadLocalMap中维护的就是一个Entry数组,每个Entry对象就是我们存放的数据,它是个key-value的形式,key就是ThreadLocal实例的弱引用,value就是我们要存放的数据,也就是一个ThreadLocal的实例会对用一个数据,形成一个键值对。

    如果有两个线程,持有同一个ThreaLocal的实例,这样的情况也就是Entry对象持有的ThreadLocal的弱引用是一样的,但是两个线程的ThreadLocalMap是不同的,记住一点,那就是ThreadLocalMap是每个线程单独维护的。

    为什么会出现内存泄漏

    那我们现在来看,为什么ThreadLocal会出现内存泄漏,我们之前也说过了,Entry对象持有的是键就是ThreadLocal实例的弱引用,弱引用有个什么特点呢?那就是在垃圾回收的时候会被回收掉,可以根据上图想一下,图中虚线就代表弱引用,如果这个ThreadLocal实例被回收掉,这个弱引用的链接也就断开了,就像这样:

    9a5d73eb9d9ea4c3fa20b4222c53a46b.png

    那么这样在Entry对象中的key就变成了null,所以这个Entry对象就没有被引用,因为key变成看null,就取不到这个value值了,再加上如果这个当前线程迟迟没有结束,ThreadLocalMap的生命周期就跟线程一样,这样就会存在一个强引用链,所以这个时候,key为null的这个Entry就造成了内存泄漏。

    因为它没有用了,但是还没有被释放。

    如何解决内存泄漏

    明白了如何产生的内存泄漏,也就知道了怎么解决,经过上面的分析,我们大致知道了在ThreadLocalMap中存在key为null的Entry对象,从而导致内存泄漏,那么只要把这些Entry都给删除掉,也就解决了内存泄漏。

    我们每次使用ThreadLocal就会随线程产生一个ThreadLocalMap,里面维护Entry对象,我们对Entry进行存取值,那么如果我们每次使用完ThreadLocal之后就把对应的Entry给删除掉,这样不就解决了内粗泄漏嘛,那怎么做呢?

    在ThreadLocal中提供了一个remove方法:

    3c1f0d8cba292ad4009ae9b65fb93799.png

    这个就是根据key删除掉对应的Entry,如此一来,我们就解决了内存泄漏问题,因为可能出现内存泄漏的Entry,在我们使用完之后就立马删除了。

    所以对于ThreadLocal而言,就应该像使用锁一样,加锁之后要记得解锁,也就是调用它的remove方法,用完就清理。


    总结

    ThreaLocalMap中存储的核心元素是Entry,Entry是一个弱引用,所以在垃圾回收的时候,ThreadLocal如果没有外部的强引用,它会被回收掉,这样就会产生key为null的Entry了,这样也就产生了内存泄漏。

    在ThreadLocal的get(),set()和remove()的时候都会清除ThreadLocalMap中key为null的Entry,如果我们不手动清除,就会造成内存泄漏,最佳做法是使用ThreadLocal就像使用锁一样,加锁之后要解锁,也就是用完就使用remove进行清理。

    认真写文章,用心做分享。
    公众号:Java耕耘者
    展开全文
  • 写在前面ThreadLocal 基本用法本文就不介绍了,...先给出结论:如果你使用不当是有可能发生内存泄露ThreadLocal 和 当前 Thread 栈堆布局图每个 Thread 里面都有一个 ThreadLocalMap,而 ThreadLocalMap 中真正承...
  • ThreadLocal内存泄露详解 1、ThreadLocal常规用法 public class ThreadLocal<T> { public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier); public void set...
  • 看了网上一大堆写ThreadLocal内存泄露的文章,全都是概念和源码内容,没有一篇模拟内存泄露的文章。所以决定自己写一个跟大家一块讨论学习。 /** * 模拟ThreadLocal内存泄露导致OOM * JVM启动参数 -Xms20M -Xmx20M...
  • 转载自 ThreadLocal 内存泄露的实例分析前言昨天分享了一篇深入分析 ThreadLocal 内存泄漏问题是从理论上分析ThreadLocal的内存泄漏问题,这一篇文章我们来分析一下实际的内存泄漏案例。分析问题的过程比结果更重要...
  • 1、首先看下ThreadLocal的原理图:在ThreadLocal的生命周期中,都存在这些引用。其中,实线代表强引用,虚线代表弱引用;2、ThreadLocal的实现:每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是...
  • ThreadLocal 内存泄露问题

    千次阅读 多人点赞 2020-07-12 21:06:06
    内存泄漏2. key=ThreadLocal是强引用3. key=ThreadLocal是弱引用4. 内存泄漏的真实原因5. key要使用弱引用 1.内存泄漏 内存溢出: Memory overflow 没有足够的内存提供申请者使用. 内存泄漏: Memory Leak 程序中已经...
  • ThreadLocal内存泄露问题本质分析:接着ThreadLocal继续探究,在上一次深入理解JVM-ThreadLocal流程深入分析ThreadLocal对于WeakReference的使用分析已经对于它里面的Entry要用WeakReference意义进行了分析,这一次...
  • NULL 博文链接:https://liuinsect.iteye.com/blog/1827012
  • 前言在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮...
  • 前言在分析ThreadLocal导致的内存泄露前,需要普及了解一下内存泄露、强引用与弱引用以及GC回收机制,这样才能更好的分析为什么ThreadLocal会导致内存泄露呢?更重要的是知道该如何避免这样情况发生,增强系统的健壮...
  • 本文转载于SegmentFault社区作者:莫小点还有救1. ThreadLocal简介通常情况下,我们创建的变量是可以...ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子...
  • 今天笔者给大家带来的是在多线程开发中经常出现的ThreadLocal还有关于这个类会带来的那些关于“内存泄露”的那些事。关于ThreadLocal首先我们看看这个ThreadLocal究竟是什么呢?下面是jdk文档的介绍:jdk1.8 /** * ...
  • ThreadLocal和Synchonized都用语解决多线程并发访问的,可以ThreadLocal与Synchonzied有本质的差别,synchoronized是利用锁的机制,使变量或代码块仅仅能被一个线程访问。而ThreadLoacal为每个线程都提供了变量的...
  • 更多参考: 对ThreadLocal实现原理的一点思考
  • ThreadLocal使用不规范,师傅两行泪组内来了一个实习生,...后台监控服务发现内存一直在缓慢上升,初步怀疑是内存泄露。把实习生的PR都找出来仔细review,果然发现问题了。由于公司内部代码是保密的,这里简单写一个...
  • 谈一谈不常见却又不可少的ThreadLocal在写ThreadLocal之前,需要先巩固下一点相关知识:Java内存模型及共享变量的可见性。内存模型中所有变量存储在主内存中,当一个线程中要使用某个变量时,需要从主内存复制该变量...
  • 1.内存泄漏 内存溢出: Memory overflow 没有足够的内存提供申请者使用. 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至...
  • 如果这个变量被设置为Null(并且立即GC回收),而ThreadLocalMap中该threadLocal没有将它移除,那么将导致内存泄露. 可造成内存泄露代码: main(){ tl = new ThreadLocal(); tl.set(new Byt

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 564
精华内容 225
关键字:

threadlocal内存泄露