首页 > 技术文章 > JDK源码之Reference类分析

houzheng 2020-06-10 12:11 原文

一 Reference抽象类

概述

在JDK1.2之前,Java中的引用的定义是十分传统的:如果reference类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。
在这种定义之下,一个对象只有被引用和没有被引用两种状态。
实际上,我们更希望存在这样的一类对象:当内存空间还足够的时候,这些对象能够保留在内存空间中;如果当内存空间在进行了垃圾收集之后还是非常紧张,则可以抛弃这些对象。
基于这种特性,可以满足很多系统的缓存功能的使用场景。
java.lang.ref包是JDK1.2引入的
引入此包的作用是对引用的概念进行了扩充,将引用分为

  1. 强引用(FinalReference)
  2. 软引用(Soft Reference)
  3. 弱引用(Weak Reference)
  4. 虚引用(Phantom Reference)

四种引用的强度按照下面的次序依次减弱:
FinalReference > SoftReference > WeakReference > PhantomReference
基于此强引用,软引用、弱引用和虚引用都是Reference抽象类的直接子类

核心源码分析

    /**
     *  Reference就是引用类型,Java虚拟机中有三种引用类型:类类型(class type)、数组类型(array type)和接口类型,引用类型的值其实就是实例在堆内存上的地址,可以把引用近似理解为指针
     *  对JVM的垃圾收集活动敏感(当然,强引用可能对垃圾收集活动是不敏感的),
     *  Reference的继承关系或者实现是由JDK定制,引用实例是由JVM创建,所以自行继承Reference实现自定义的引用类型是无意义的,但是可以继承已经存在的引用类型
     *  Reference是所有引用对象的基类。这个类定义了所有引用对象的通用操作,就像Integer类之于int类型
     */
    public abstract class Reference<T> {

        //Reference保存的引用指向的对象
        private T referent;

        /**
         * 当一个Reference对象绑定的对象被GC回收时,JVM会将该引用对象被绑定到的reference对象(this)推入此队列。
         * 其他程序可以通过轮询此队列,来获得该注册对象被GC的的“通知”,并完成一些工作
         * 如WeakHashMap可以"知道"被GC的Entry并将其从Map中移除
         * 实际只是逻辑上的一个标志,标志该对象是否加入到了队列。
         * 队列里的Reference对象是通过next属性组成链式循环队列
         */
        volatile ReferenceQueue<? super T> queue;

        //下一个Reference实例的引用,Reference实例通过此构造单向的链表
        volatile Reference next;

        // 注意这个属性由transient修饰,基于状态表示不同链表中的下一个待处理的对象,主要是pending-reference列表的下一个元素,通过JVM直接调用赋值
        transient private Reference<T> discovered;  /* used by VM */

        // 静态内部类,同步锁使用的锁对象,学习了
        static private class Lock { }
        private static Reference.Lock lock = new Reference.Lock();

        // 获取持有的referent实例
        public T get() {
            return this.referent;
        }

        // 把持有的referent实例置为null
        public void clear() {
            this.referent = null;
        }

        // 引用对象入队
        public boolean enqueue() {
            return this.queue.enqueue(this);
        }

        // 构造器
        Reference(T referent) {
            this(referent, null);
        }
        Reference(T referent, ReferenceQueue<? super T> queue) {
            this.referent = referent;
            this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
        }
    }

二 四种引用类型

引用类型概述

Java数据类型分为两大类:

  1. 8种基本类型 (primitive type)
    • byte
    • short
    • int
    • long,
    • float
    • double
    • char
    • boolean
  2. 三种引用类型(reference):
    • 类类型(class type)
    • 数组类型(array type)
    • 接口类型(interface type)

这些引用类型的值分别指向动态创建的类实例、数组示例和实现了某个接口的类示例或数组示例。
可见,引用类型的值其实就是实例在堆内存上的地址,可以把引用近似理解为指针。
3. JVM把引用类型分为四种类型:强引用、软引用、弱引用、虚引用
引用的类型可以描述它所指向的实例的可达性,进而供垃圾回收器根据不同类型做出不同的处理的能力,同时也提供了编程者跟踪对象生命周期的功能。
描述不同的引用类型,由Reference类的子类来实现:
- FinalReference(强引用)
- SoftReference (软引用)
- WeakReference (弱引用)
- PhantomReference (虚引用)

1. FinalReference 强引用

强引用是指创建一个对象并它赋值给一个引用,引用是存在JVM中的栈(还有方法区)中的。具有强引用的对象,垃圾回收器绝对不会去回收它,直到内存不足以分配时,抛出OOM。
大多数情况,我们new一个对象,并把它赋值给一个变量,这个变量就是强引用,源码中只有一个构造器,这里不再列举

    /**
     * 以下都是强引用
     */
    // 方法区中的类静态属性引用的对象
    private static Object finalRet2 = new Object();
    // 方法区中的常量引用的对象
    private static final Object finalRet3 = new Object();

    public static void main(String[] args) {
        // 栈上的局部变量引用的对象
        Object finalRet1 = new Object();
    }

2. SoftReference 软引用

软引用描述一些还有用但非必需的对象,具有软引用关联的对象,内存空间足够时,垃圾回收器不会回收它。
当内存不足时(接近OOM),垃圾回收器才会去决定是否回收它。
软引用一般用来实现简单的内存缓存
代码测试:

public class MyTest02 {
    // 类对象
    class UserTest {
        // 模拟内存占用3M,以更好观察gc前后的内存变化
        private byte[] memory = new byte[3*1024*1024];
    }
    /**
     * 测试弱引用在内存足够时不会被GC,在内存不足时才会被GC的特性
     * JVM参数 -Xms10m -Xmx10m -XX:+PrintGCDetails  将内存大小限制在20M,并打印出GC日志
     */
    public void testSoftReference(){
        // 创建弱引用类,将该引用绑定到弱引用对象上
        SoftReference<UserTest> sortRet = new SoftReference<>(new UserTest());
        // 此时并不会被GC.内存足够
        System.gc();
        System.out.println("GC后通过软引用重新获取了对象:" + sortRet.get());

        // 模拟内存不足,即将发生OOM时,软引用会被回收,获取为null
        List<UserTest> manyUsers = new ArrayList<>();
        for(int i = 1; i < 100000; i++){
            System.out.println("将要创建第" + i + "个对象");
            manyUsers.add(new UserTest());
            System.out.println("创建第" + i + "个对象后, 软引用对象:" + sortRet.get());
        }
    }

    public static void main(String[] args) {
        MyTest02 referenceTest = new MyTest02();
        referenceTest.testSoftReference();
    }
}

打印结果:

3. WeakReference 弱引用

弱引用描述非必需对象,但它的强度比软引用更弱一些。
WeakReference对其引用的对象并无保护作用,当垃圾回收器进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
使用举例:

    public static void main(String[] args) {
        //通过WeakReference的get()方法获取弱引用对象
        WeakReference<User> userWeakReference = new WeakReference<>(new User("hou"));
        System.out.println("User:" + userWeakReference.get());
        System.gc();
        try {
            //休眠一下,在运行的时候加上虚拟机参数-XX:+PrintGCDetails,输出gc信息,确定gc发生了。
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //如果为空,代表被回收了,如果是强引用, User user=new User("name"); 则不会回收
        if (userWeakReference.get() == null) {
            System.out.println("user已经被回收");
        }
    }

4. PhantomReference 虚引用

虚引用也被称为幽灵引用或幻引用,它是最弱的一种引用关系。
虚引用并不会影响对象的GC,而且并不可以通过PhantomReference对象取得一个引用的对象。
虚引用唯一的作用则是利用其必须和ReferenceQueue关联使用的特性,当其绑定的对象被GC回收后会被推入ReferenceQueue,外部程序可以通过对此队列轮询来获得一个通知,以完成一些目标对象被GC后的清理工作。
PhantomReference 的构造方法,与SoftReference和WeakReference不同,他的构造必须传入一个ReferenceQueue,并且get方法返回null

    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }

推荐阅读