• 如何理解java中的Reference和引用类型?正确的案例讲解


    简介

    java中有值类型也有引用类型,引用类型一般是针对于java中对象来说的,今天介绍一下java中的引用类型。java为引用类型专门定义了一个类叫做Reference。Reference是跟java垃圾回收机制息息相关的类,通过探讨Reference的实现可以更加深入的理解java的垃圾回收是怎么工作的。

    本文先从java中的四种引用类型开始,一步一步揭开Reference的面纱。

    java中的四种引用类型分别是:强引用,软引用,弱引用和虚引用。

    另外要注意:要突破高薪Java架构项目经验永远是核心,如果你没有最新JAVA架构实战教程及大厂30k+面试宝典,可以去小编的Java架构学习.裙 :七吧伞吧零而衣零伞 (数字的谐音)转换下可以找到了,里面很多新JAVA架构项目教程,还可以跟老司机交流讨教! 

    强引用Strong Reference

    java中的引用默认就是强引用,任何一个对象的赋值操作就产生了对这个对象的强引用。

    我们看一个例子:

    public class StrongReferenceUsage {
    
        @Test
        public void stringReference(){
            Object obj = new Object();
        }
    }
    复制代码

    上面我们new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。

    强引用的特性是只要有强引用存在,被引用的对象就不会被垃圾回收。

    软引用Soft Reference

    软引用在java中有个专门的SoftReference类型,软引用的意思是只有在内存不足的情况下,被引用的对象才会被回收。

    先看下SoftReference的定义:

    public class SoftReference<T> extends Reference<T>
    复制代码

    SoftReference继承自Reference。它有两种构造函数:

        public SoftReference(T referent) 
    复制代码

    和:

        public SoftReference(T referent, ReferenceQueue<? super T> q) 
    复制代码

    第一个参数很好理解,就是软引用的对象,第二个参数叫做ReferenceQueue,是用来存储封装的待回收Reference对象的,ReferenceQueue中的对象是由Reference类中的ReferenceHandler内部类进行处理的。

    我们举个SoftReference的例子:

        @Test
        public void softReference(){
            Object obj = new Object();
            SoftReference<Object> soft = new SoftReference<>(obj);
            obj = null;
            log.info("{}",soft.get());
            System.gc();
            log.info("{}",soft.get());
        }
    复制代码

    输出结果:

    22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
    22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
    复制代码

    可以看到在内存充足的情况下,SoftReference引用的对象是不会被回收的。

    弱引用weak Reference

    weakReference和softReference很类似,不同的是weekReference引用的对象只要垃圾回收执行,就会被回收,而不管是否内存不足。

    同样的WeakReference也有两个构造函数:

    public WeakReference(T referent);
    
     public WeakReference(T referent, ReferenceQueue<? super T> q);
    复制代码

    含义和SoftReference一致,这里就不再重复表述了。

    我们看下弱引用的例子:

        @Test
        public void weakReference() throws InterruptedException {
            Object obj = new Object();
            WeakReference<Object> weak = new WeakReference<>(obj);
            obj = null;
            log.info("{}",weak.get());
            System.gc();
            log.info("{}",weak.get());
        }
    复制代码

    输出结果:

    22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
    22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null
    复制代码

    我们看到gc过后,弱引用的对象被回收掉了。

    虚引用PhantomReference

    PhantomReference的作用是跟踪垃圾回收器收集对象的活动,在GC的过程中,如果发现有PhantomReference,GC则会将引用放到ReferenceQueue中,由程序员自己处理,当程序员调用ReferenceQueue.pull()方法,将引用出ReferenceQueue移除之后,Reference对象会变成Inactive状态,意味着被引用的对象可以被回收了。

    和SoftReference和WeakReference不同的是,PhantomReference只有一个构造函数,必须传入ReferenceQueue:

    public PhantomReference(T referent, ReferenceQueue<? super T> q)
    复制代码

    看一个PhantomReference的例子:

    @Slf4j
    public class PhantomReferenceUsage {
    
        @Test
        public void usePhantomReference(){
            ReferenceQueue<Object> rq = new ReferenceQueue<>();
            Object obj = new Object();
            PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
            obj = null;
            log.info("{}",phantomReference.get());
            System.gc();
            Reference<Object> r = (Reference<Object>)rq.poll();
            log.info("{}",r);
        }
    }
    复制代码

    运行结果:

    07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
    07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db
    复制代码

    我们看到get的值是null,而GC过后,poll是有值的。

    因为PhantomReference引用的是需要被垃圾回收的对象,所以在类的定义中,get一直都是返回null:

        public T get() {
            return null;
        }
    复制代码

    Reference和ReferenceQueue

    讲完上面的四种引用,接下来我们谈一下他们的父类Reference和ReferenceQueue的作用。

    Reference是一个抽象类,每个Reference都有一个指向的对象,在Reference中有5个非常重要的属性:referent,next,discovered,pending,queue。

    private T referent;         /* Treated specially by GC */
    volatile ReferenceQueue<? super T> queue;
    Reference next;
    transient private Reference<T> discovered;  /* used by VM */
    private static Reference<Object> pending = null;
    复制代码

    每个Reference都可以看成是一个节点,多个Reference通过next,discovered和pending这三个属性进行关联。

    先用一张图来对Reference有个整体的概念:

    referent就是Reference实际引用的对象。

    通过next属性,可以构建ReferenceQueue。

    通过discovered属性,可以构建Discovered List。

    通过pending属性,可以构建Pending List。

    四大状态

    在讲这三个Queue/List之前,我们先讲一下Reference的四个状态:

    从上面的图中,我们可以看到一个Reference可以有四个状态。

    因为Reference有两个构造函数,一个带ReferenceQueue,一个不带。

    
        Reference(T referent) {
            this(referent, null);
        }
    
        Reference(T referent, ReferenceQueue<? super T> queue) {
            this.referent = referent;
            this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
        }
    复制代码

    对于带ReferenceQueue的Reference,GC会把要回收对象的Reference放到ReferenceQueue中,后续该Reference需要程序员自己处理(调用poll方法)。

    不带ReferenceQueue的Reference,由GC自己处理,待回收的对象其Reference状态会变成Inactive。

    创建好了Reference,就进入active状态。

    active状态下,如果引用对象的可到达状态发送变化就会转变成Inactive或Pending状态。

    Inactive状态很好理解,到达Inactive状态的Reference状态不能被改变,会等待GC回收。

    Pending状态代表等待入Queue,Reference内部有个ReferenceHandler,会调用enqueue方法,将Pending对象入到Queue中。

    入Queue的对象,其状态就变成了Enqueued。

    Enqueued状态的对象,如果调用poll方法从ReferenceQueue拿出,则该Reference的状态就变成了Inactive,等待GC的回收。

    这就是Reference的一个完整的生命周期。

    三个Queue/List

    有了上面四个状态的概念,我们接下来讲三个Queue/List:ReferenceQueue,discovered List和pending List。

    ReferenceQueue在讲状态的时候已经讲过了,它本质是由Reference中的next连接而成的。用来存储GC待回收的对象。

    pending List就是待入ReferenceQueue的list。

    discovered List这个有点特别,在Pending状态时候,discovered List就等于pending List。

    在Active状态的时候,discovered List实际上维持的是一个引用链。通过这个引用链,我们可以获得引用的链式结构,当某个Reference状态不再是Active状态时,需要将这个Reference从discovered List中删除。

    WeakHashMap

    最后讲一下WeakHashMap,WeakHashMap跟WeakReference有点类似,在WeakHashMap如果key不再被使用,被赋值为null的时候,该key对应的Entry会自动从WeakHashMap中删除。

    我们举个例子:

        @Test
        public void useWeakHashMap(){
            WeakHashMap<Object, Object> map = new WeakHashMap<>();
            Object key1= new Object();
            Object value1= new Object();
            Object key2= new Object();
            Object value2= new Object();
    
            map.put(key1, value1);
            map.put(key2, value2);
            log.info("{}",map);
    
            key1 = null;
            System.gc();
            log.info("{}",map);
    
        }
    复制代码

    输出结果:

    [main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc, java.lang.Object@11028347=java.lang.Object@1f89ab83}
    [main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc}
    复制代码

    可以看到gc过后,WeakHashMap只有一个Entry了。

    总结注意:要突破高薪Java架构项目经验永远是核心,如果你没有最新JAVA架构实战教程及大厂30k+面试宝典,可以去小编的Java架构学习.裙 :七吧伞吧零而衣零伞 (数字的谐音)转换下可以找到了,里面很多新JAVA架构项目教程,还可以跟老司机交流讨教! 

    本文的文字及图片来源于网络加上自己的想法,仅供学习、交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理

  • 相关阅读:
    jvm调试相关:jmap失效下找到alternatives神器
    作业2:java内存模型图示
    Python脚本:Linux自动化执行Python脚本
    Nodejs:单线程为什么能支持高并发?
    作业1:java虚拟机内存模型图示
    SpringBoot中获取spring.profiles.active的值
    线程中使用注解出现空指针如何解决?
    java7:核心技术与最佳实践读书笔记——对象生命周期
    java7:核心技术与最佳实践读书笔记——类加载
    虚拟机上不了网啦
  • 原文地址:https://www.cnblogs.com/chengxuyuanaa/p/12842470.html
Copyright © 2020-2023  润新知