JDK源码 Java Reference

更多请移步: 我的博客

JDK源码 Java的四种Reference

之前探讨过一次JAVA的FinalReference,这次我们来看下java.lang.ref包下对应的其他三种引用。

走近引用

Reference和ReferenceQueue在使用中一定是结伴出现的,当一个Reference确定要被GC回收,GC便会把Reference加入到与之关联的ReferenceQueue中。注意:在Reference的构造方法中,我们可以传入一个注册队列ReferenceQueue,这个队列我们稍后会具体看,需要主要的是,这个队列需要单独的线程去做消费,否则会存在OOM的隐患。

这些引用可用来实现不同的缓存类型(内存敏感和内存不敏感),大名鼎鼎的Guava cache就是基于引用的这些特性来实现高速本地缓存。

StrongReference(强引用)

我们平时开发中new一个对象出来,这种引用便是强引用。 JVM 系统采用 Finalizer 来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法。具体详见我的前一篇博客:JDK源码 FinalReference

SoftReference(软引用)

当内存足够的时候,软引用所指向的对象没有其他强引用指向的话,GC的时候并不会被回收,当且只当内存不够时才会被GC回收(调用finalize方法)。强度仅次于强引用。GC回收前,会将那些已经向引用队列注册的新清除的软引用加入队列。

public class ClassSoft {

    public static class Referred {
        /**
         * 不是必须实现,和Strong不同。
         * 实现该方法是为了追踪GC,
         * 实现后也会被当作Finalizer
         * @throws Throwable
         */
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Referred对象被垃圾收集");
        }

        @Override
        public String toString() {
            return "I am Referred";
        }
    }

    public static void collect() throws InterruptedException {
        System.gc();
        Thread.sleep(2000);
    }

    static class CheckRefQueueThread extends Thread{
        @Override
        public void run() {
            Reference<Referred> obj = null;
            try {
                obj = (Reference<Referred>)softQueue.remove();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(obj != null) {
                try {
                    Field referent = Reference.class.getDeclaredField("referent");
                    referent.setAccessible(true);
                    Object result = referent.get(obj);
                    //此处异常可以说明,在被放入队列之前referent已经被JVM置为null
                    System.out.println("gc will collect: " + result.getClass() + "@" + result.hashCode());
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("Object for SoftReference is " + obj.get());
            }
        }
    }

    //如果我们使用了自定义的注册队列,一定要启动一个线程来处理该队列
    //JVM只负责像队列中放入对象,不负责清理
    static ReferenceQueue<Referred> softQueue = new ReferenceQueue<>();

    /**
     * JVM配置
     * -Xms4m -Xmx4m
     * -XX:+PrintGCDetails -Xloggc:/Users/childe/logs/gc-f.log
     * 务必加上该参数,以确定collect方法后GC被执行
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("创建软引用");

        Referred strong = new Referred();
        SoftReference<Referred> soft = new SoftReference<>(strong,softQueue);
        new CheckRefQueueThread().start();

        ClassSoft.collect();

        System.out.println("切断强引用");

        strong = null;
        ClassSoft.collect();

        System.out.println("GC之前,软引用值:" + soft.get().toString());

        System.out.println("开始堆占用");
        try {
            List<byte[]> bytes = new ArrayList<>();
            while (true) {
                bytes.add(new byte[1024*1024]);
                ClassSoft.collect();
            }
        } catch (OutOfMemoryError e) {
            // 软引用对象应该在这个之前被收集
            System.out.println("内存溢出...");
        }

        System.out.println("Done");
    }
}

程序输出如下:

创建软引用
切断强引用
GC之前,软引用值:I am Referred
开始堆占用
java.lang.NullPointerException
Referred对象被垃圾收集
    at com.cxd.jvm.references.ref.ClassSoft$CheckRefQueueThread.run(ClassSoft.java:54)
Object for SoftReference is null
内存溢出...
Done

我们可以看到,软引用在GC回收前,调用get方法是可以返回其关联的实际对象的,当其被GC加入ReferenceQueue前,JVM会将其关联的对象置为null。

WeakReference(弱引用)

弱引用指向的对象没有任何强引用指向的话,GC的时候会进行回收。

/**
 *
 * Created by childe on 2017/3/31.
 */
public class ClassWeak {
    public static class Referred {
        /**
         * 不是必须实现,和Strong不同。
         * 实现该方法是为了追踪GC
         * 实现后也会被当作Finalizer
         * @throws Throwable
         */
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Referred对象被垃圾收集");
        }

        @Override
        public String toString() {
            return "I am weak";
        }
    }

    public static void collect() throws InterruptedException {
        System.gc();
        Thread.sleep(2000);
    }

    /**
     * JVM配置
     * -XX:+PrintGCDetails -Xloggc:/Users/childe/logs/gc-f.log
     * 务必加上该参数,以确定collect方法后GC被执行
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("创建一个弱引用");

        Referred strong = new Referred();
        WeakReference<Referred> weak = new WeakReference<>(strong);

        ClassWeak.collect();
        System.out.println("切断强引用");

        strong = null;

        System.out.println("GC之前,弱引用值:" + weak.get().toString());

        ClassWeak.collect();

        System.out.println("Done");
    }
}

程序输出如下:

创建一个弱引用
切断强引用
GC之前,弱引用值:I am weak
Referred对象被垃圾收集
Done
PhantomReference(虚引用)

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。这个特性,决定了他的get方法每次调用均会返回null。

/**
 * Created by childe on 2017/3/31.
 */
public class ClassPhantom {

    public static class Referred {
        @Override
        protected void finalize() throws Throwable {
            System.out.println("Referred对象被垃圾收集");
        }

        @Override
        public String toString() {
            return "Referredqq";
        }
    }

    public static void collect() throws InterruptedException {
        System.gc();
        Thread.sleep(2000);
    }

    static class CheckRefQueueThread extends Thread{
        @Override
        public void run() {
            Reference<Referred> obj = null;
            try {
                obj = (Reference<Referred>) phantomQueue.remove();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(obj != null) {
                //因为虚引用的指示对象总是不可到达的,所以此方法总是返回 null
                System.out.println("Object for phantomReference is " + obj.get());
                try {
                    Field referent = Reference.class.getDeclaredField("referent");
                    referent.setAccessible(true);
                    Object result = referent.get(obj);
                    System.out.println("gc will collect: " + result.getClass() + "@" + result.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }

    static ReferenceQueue<Referred> phantomQueue = new ReferenceQueue<>();

    /**
     * -Xms4m -Xmx4m
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        System.out.println("创建一个软引用");

        Referred strong = new Referred();
        PhantomReference<Referred> soft = new PhantomReference<>(strong, phantomQueue);
        new CheckRefQueueThread().start();

        collect();

        System.out.println("切断强引用");

        strong = null;
        collect();

        System.out.println("开始堆占用");

        try {
            List<byte[]> bytes = new ArrayList<>();
            while (true) {
                bytes.add(new byte[1024*1024]);
                collect();
            }
        } catch (OutOfMemoryError e) {
            // 软引用对象应该在这个之前被收集
            System.out.println("内存溢出...");
        }

        System.out.println("Done");
    }
}

输出如下:

创建一个软引用
切断强引用
Referred对象被垃圾收集
开始堆占用
Object for phantomReference is null
gc will collect: class com.cxd.jvm.references.ref.ClassPhantom$Referred@Referredqq
内存溢出...
Done
引用间的差别

我们注意到虚引用在被加入到ReferenceQueue中后,关联对象并没有被置为null,这点和弱引用及软引用不同。这也是我开头说的潜在OOM的最大风险。当然,这种现象只是加速了OOM问题的暴露,并不是根本原因。JVM GC的这个模型可以看作是生产-消费模型,GC是生产者,我们自己起的线程是消费者(Finalizer中JDK自带线程),当只有生产者时,OOM是迟早的事情。

ReferenceQueue

我们介绍的这四种引用都从java.lang.ref.Reference继承,Reference是个单向链表,ReferenceQueue利用Reference的这个特性来维护先进后出单向队列(类似栈)。

public abstract class Reference<T> {
    //......
    //引用有4中概念上的状态:Active、Pending、 Enqueued 、Inactive
    //引用的初始态为Active或者Pending,它的生命后期为:(Active || Pending)-> Enqueued -> Inactive
    private T referent;         /* Treated specially by GC 由GC专门处理*/

    ReferenceQueue<? super T> queue; /* Reference 关联的引用队列 */

    Reference next; /* 指向下一个引用 */
    //......
}

public class ReferenceQueue<T> {
    //......
    //如果我们构造Reference时,未传入自定义队列,默认使用此队列。
    private static class Null extends ReferenceQueue {
        //入队操作直接返回
        boolean enqueue(Reference r) {
            return false;
        }
    }

    static ReferenceQueue NULL = new Null();
    static ReferenceQueue ENQUEUED = new Null();

    boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class 只会对Reference类调用该方法 */
        synchronized (r) {
            //以入队的引用不多次入队
            if (r.queue == ENQUEUED) return false;
            synchronized (lock) {
                //修改引用入队状态为Enqueued
                r.queue = ENQUEUED;
                //插入对头
                r.next = (head == null) ? r : head;
                head = r;
                queueLength++;
                if (r instanceof FinalReference) {
                    sun.misc.VM.addFinalRefCount(1);
                }
                //通知等待在锁上的线程ReferenceQueue.remove()
                lock.notifyAll();
                return true;
            }
        }
    }

    private Reference<? extends T> reallyPoll() {       /* Must hold lock 必须在持有lock锁的情况下执行,lock由其外层方法获取 */
        if (head != null) {
            //获取队头
            Reference<? extends T> r = head;
            head = (r.next == r) ? null : r.next;
            //将关联的队列置为NULL,此时r的状态为Inactive,处于此状态的引用不会再发生变化,等待被回收。
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }
    //......
}
扩展WeakHashMap

JDK中有对引用的具体使用,当我们需要实现一个简单的本地内存敏感缓存时,可以考虑使用WeakHashMap,此处不再分析其源码。WeakHashMap的每个Entry都是WeakReference的子类,每次put或者get或者resize扩容时,都会调用WeakHashMap的expungeStaleEntries方法,清除那些被GC加入到ReferenceQueue的Entry。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值