• JDK源码之Reference类分析


    一 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);
        }
    
  • 相关阅读:
    DOMContentLoaded
    闭包之外的解决方法
    前端单页面应用路由
    zepto的tap事件点透问题
    微服务实践指南☞Kong网关的简介和安装
    Visual studio docker build no such file or directory
    Saas物联网共享平台实战
    dotnetty源码解读一些要点
    Ubuntu 安装 JDK 7 / JDK8 的两种方式
    spring入门笔记-(一)、spring boot HelloWorld
  • 原文地址:https://www.cnblogs.com/houzheng/p/13084237.html
Copyright © 2020-2023  润新知