Java的四种引用以及ThreadLocal源码分析

Posted by     "Eric" on Wednesday, March 11, 2020
  1. 强引用:

    只有所有的GC Roots对象都不通过强引用引用对象时,该对象才能被回收

  2. 软引用

    当内存不足时,GC会把软引用指向的对象回收。软引用被经常用在缓存上,如下面这个例子

    // list --> SoftReference —-> byte[]
    // list对SoftReference是强引用,但对SoftReference对byte[]是软引用
    List<SoftReference<Byte[]>> list=new ArrayList<>();
    ReferenceQueue<byte[]> queue=new ReferenceQueue<>();//引用队列
    for(int i=0;i<5;++i){
    	//关联了引用队列,当软引用所关联的byte[]回收时,软引用自己也会加入到queue中去
    		SoftReference<Byte[]> ref=new SoftReference<>(new Byte[_4MB]);
    		list.add(ref);
    }
    //从list中删除掉无效的引用
    Reference<? Extends byte[]> poll=queue.poll();
    while(poll!=null){
    		list.remove(poll);
    		poll=queue.poll();
    }
    
  3. 弱引用

    遇到GC就会被回收,如果一个强引用和弱引用同时指向对象。当强引用消失时,该对象也会被回收,一般会用在容器中。

    通过弱引用可以解决,如该篇文章中提到的内存泄漏问题中的第一种情况,我们可以在Vector容器中添加一个对象的弱引用而不是强引用。这样当该对象没有被强引用指向时,就会被回收。如下所示

    public class solution{
     static Vector<WeakReference<?>> v=new Vector<>();
        public static void main(String[] args) {
         Test t=new Test();
            TestReference tp=new TestReference(t);
            v.add(tp);				//在Vector中放入的是Test对象的软引用对象
            t=null;					//当强引用不再指向Test对象时候,即使有弱引用指向了该对象,gc时会回收
            System.gc();            //Output:我被回收了
        }
    
    }
    
    //定义一个指向Test的弱引用类型
    class TestReference extends WeakReference<Test>{
        TestReference(Test t){
            super(t);         //将弱引用指向一个Test对象
        }
    
    }
    
    //定义一个Test类型,重写finalize方法,该方法在对象被回收时会被调用
    class Test{
        @Override
        protected void finalize() throws Throwable {
            System.out.print("我被回收了");
        }
    }
    

    上面这一原理也被用在了ThreadLocal中

    //可以在类的成员变量中声明一个ThreadLocal对象,该对象在每个线程中都独占一份
    ThreadLocal<T> tl = new ThreadLocal<>();
    

    通过使用ThreadLocal的set方法,可以进行赋值操作,其源码为

    public void set(T value) {
        Thread t = Thread.currentThread(); //拿到当前线程
        /*
        *  ThreadLocalMap是定义在ThreadLocal中的一个内部类,是一个Map结构
        *  在Thread类中会持有一个ThreadLocalMap的成员变量,因此该Map是每个
        *  Thread所独有的
        */
        ThreadLocalMap map = getMap(t);  
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
    

    ThreadLocalMap和HashMap的结构类似,通过查看源码可以理解为一个简化版的HashMap。ThreadLocalMap由一个Entry[]构成,而Entry是ThreadLocalMap的内部类,源码如下所示

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    该类继承了WeakReference,并且在初始化时把一个弱引用指向了一个ThreadLocal对象作为Key值,也就是说Entry其实是一个k-v对,使用弱引用的原因与上面Vector容器的例子是类似的,是为了防止内存泄漏。如果没有强引用指向ThreadLocal对象时,ThreadLocal对象可以被回收。

    image-20200825191637807

    但是ThreadLocal中同样会存在一个,当指向ThreadLocal对象的强引用消失后,ThreadLocal对象会被回收,但是ThreadLocalMap这一结构还存在,并且之前的记录还会存在且Value指向了一个对象,因此一个好习惯是不再是要用ThreadLocal对象时,使用tl.remove()将该条记录删除。

  4. 虚引用

    PhantomReference<M> phantomReference = new PhantomReference<>(new M(), Queue);
    

    注意,PhantomReference必须与一个引用队列相关联,并且无法通过phantomReference取值。当虚引用指向的对象被回收时,虚引用自己会被加入到引用队列中,可以通过再开一个线程不断地查看这个队列中是否有值做出相应的操作。其常用在对直接内存的回收场景下。

    例如NIO包中的ByteBuffer类

    8kmCXn.png