ThreadLocal内存如何释放

是ThreadLocal是web中线程中存储变量 传送数据的神奇,因为web中每个请求都一个是线程,所以可以理解为一次请求到结束都是可以在ThreadLocal中存储 获取的(不包括异步、响应式webflux),因为他们整个请求不是在同一线程

#ThreadLocal那么原理是什么#

在Thread里有个threadLocals 字段,类型为ThreadLocal.ThreadLocalMap

ThreadLocalMap为一个手写map,里面有个数组 table,类型为ThreadLocal.ThreadLocalMap.

Entry

 
  

这个Entry是内在释放关键,晚点说

线程启动后会有个字段threadLocals存储信息,那么和 ThreadLocal有什么关系?

其实这里设计有点怪,也许才是精髓  

ThreadLocal有两层作用

1 更像是个工具类,类里方法去控制调用 Thread中的 threadLocals字段的 

2 是做为ThreadLocalMap中的一个key,  我们知道map都是由key和value组成

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

所以说有点怪,做为一个类利用自己为key 把自己存在别的类里

平时我们实现可能都会分开,再用一个静态类,或别的管理类来做这个事情

如写一个 ThreadLocalStore  里面实现

public class ThreadLocalStore {
    
    public static   void set(ThreadLocal key,T value ) {
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = t.threadLocals;
        if (map != null)
            map.set(key, value);
        else
            createMap(t,key, value);
    }

    static   void createMap(Thread t,ThreadLocal key, T firstValue) {
        t.threadLocals = new ThreadLocal.ThreadLocalMap(key, firstValue);
    }


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


    private  T setInitialValue(ThreadLocal key) {
        
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = t.threadLocals;
        createMap(t, key,null);
        return null;
    }
}

上面这个是仿照ThreadLocal原来set get写的

ThreadLocal  threadLocal= new ThreadLocal();

使用时就是

ThreadLocalStore.set(threadLocal,"test"));      ThreadLocalStore.get(threadLocal);

可能这样更像我们平时写代码的设计,按这样看,threadLocal的原理就很简单了吧

下文说的GC,包括youngGc和FullGc

现在我们看一下ThreadLocalMap.Entry

static class Entry extends WeakReference> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

继承了WeakReference 弱引用

弱引用应该都知道,如果发生了GC会回收弱引用,但有一点要注意,就是这个弱引用并不是说发生GC就会被回收,还要满足这个引用的对象没有再被强引用时才会

public void test(){
    WeakReference myWeakReference = new WeakReference(new Object()); 
    //a点
    Object obj=myWeakReference.get();
    //b点
} 
  

比如上面的代码里,obj被获取出来了后,这个obj如果被一直传递或使用时,就算GC,myWeakReference弱引用里的Object就不会被收回。   所以a点可以被回收,c点不行

public void test2(){
    WeakReference myWeakReference = new WeakReference(new Object());
    System.out.println(myWeakReference.get());
}

上面的代码仅get了弱引用,没有再赋值,只要发生GC 就会被回收

所以弱引用的对象有没有被再赋值 强引用很关键!

public void test3(){
    WeakReference myWeakReference = new WeakReference(new Object());
    //a点
    childTest(myWeakReference);
    //b点
}

public void childTest(WeakReference myWeakReference){
    //c点
    Object obj=myWeakReference.get();
    //d点
    System.out.println(obj);
    //d点
}

上面的代码a点,b点,c点发生GC都被回收弱引用的对象,但只要childTest内d点以后时发生GC都不会回收

因为childTest里赋值 强引用了obj,只有等到childTest方法执行完 obj作用域完没有被引用时,发生GC才会回收弱引用

当然弱引用里还有个clear方法,这个意义就是把我们new的弱引用对象和 引用对象本身关系解开,就是引用的对象生命周期还是他原来的周期,和弱引用没有任何关系了,该是什么还是什么

如果这些你明白了,再来看ThreadLocalMap里的Entry类  key是被弱引用包裹,value正常字段

        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

所以value和弱引用什么的就没什么关系  只是一个普通字段,只要Entry对象不回收value永远也不回收

key的话就是你创建的ThreadLocal对象,如果没有在别的地方有强引用了,GC是Key就会被回收,也就是你new 的ThreadLocal对象,发生GC随时都可能被回收掉

那么key随时被回收,为什么我们代码里从不会发生 Gc后 ThreadLocal取不到值的情况,刚才认真看的人应该已经知道了, 因为ThreadLocal是我们创建的,一直存在强引用,,如果你创建的是静态的 那么程序里永远也不会回收掉

static ThreadLocal threadLocal=new ThreadLocal();

public Object getVal(){
    threadLocal.get();
}

public void setVal(Object obj){
    threadLocal.set(obj);
} 
  

又绕回来,如果你的ThreadLocal在ThreadLocalMap.Entry的弱引用回收了,那说明ThreadLocal已经不存在任何引用,作用域已经结果了,你自己肯定也取不到创建的ThreadLocal对象了

    public void test(){
        test3();
        //a
    }

    public void test3(){
        ThreadLocal threadLocal = new ThreadLocal();
        threadLocal.set(new Object());
    } 
  
在test3执行完完后a点你的threadLocal可能被回收了,但在a点你获取不到threadLocal了呀, 如果获取到了那threadLocal就是有强引用的 也不可能被回收

理论上当ThreadLocal生命周期结束后,你存进去的value也没有什么用了应该被回收,因为你没有key,永远也取不到value了,但现实不是。就算发生GC后  存进去的Object有时还会存在,猜测是个bug

看看GC发生了什么

当执行GC完后 如果ThreadLocal没有被强引用,那么Thread内的map(也就是threadLocals字段ThreadLocalMap类型)  内的值Entry  的key会被回收,但Entry不会被回收 value也不会被回收,Entry是在map内的值,map不移除它引用就在。那什么情况被回收,下面前题是value本身没有被引用

1 如果你调用了ThreadLocal.remove()方法,当remove后 value被map移除了,自然没有引用可回收

2 线程销毁结束(线程池里的线程一般是不销毁的,所以正常是不会回收value),线程结束会把

threadLocals=null,那么map内所有数据都没有引用,可被回收

3 gc后,Thread内的map内的值Entry  的key会被回收, map本身set, remove时

会调用到expungeStaleEntry方法,删除Entry里key为空的,此时value也没有引用 可被回收

但是3里 set 时判断map有扩容时才会执行expungeStaleEntry,所以value偶尔被回收,偶尔不会,而且这个判断扩容好像有问题(没有深入研究,也可能是GC是异步的导致map大小异常)导致后面就算有扩容也有部分对象永远不回收 

总结:

        如果你是new 的ThreadLocal那么就记得remove ,尤其在线程池和web程序里,因为线程不会销毁ThreadLocal只能靠同一线程里下次设置TheadLocal,set时才可能清理

         尽量创建static的ThreadLocal,这样保证一个线程就一个TreadLocal,就算不remove基本也不会引起内存泄露,因为ThreadLocal和线程数一样多

你可能感兴趣的:(java,java)