• 玩转Reference Java引用类型


    Reference

    引用类型 抽象父类,java.lang.ref包下

    作用: GC时通过GC Root可达性分析+引用类型来判断对象是否应该回收

    先上结论

    没有被Reference引用的对象默认为强引用

    强引用:GC时通过GC Root可达性分析判断,只要被GC Root链路关联则不回收  (顺便说一下GC Root根对象,一般就是静态变量和当前栈空间的局部变量)  

    软引用:GC时仅有被软引用的对象,判断此时JVM内存是否充裕来决定对象是否回收

    弱引用:GC时仅有弱引用的对象都会被回收

    虚引用:回收级别相当于强引用,当GC时如果对象仅有虚引用时会被回收   但是是假回收 在Java 8以及之前的版本中,在虚引用回收后,虚引用指向的对象才会回收。在Java 9以及更新的版本中,虚引用不会对对象的生存产生任何影响。

    而虚引用的作用在于 GC时将被虚引用的对象加入到引用队列,用于调用者判断某个对象是否被回收来做一些事情

    over

    下面详细剖析

    Reference的几种状态

    Reference的四种状态  (以下由Ref代替Reference 要不太累了)

     Ref刚创建时属于active状态。当GC回收时由GC线程将其加到pending队列,变成pending状态。唤醒Ref内部的ReferenceHandler线程,判断是否指定了Ref队列,进入Enqueued状态或Inactive状态。

     

    Reference源码分析

    先看属性

    referenct:被引用的对象

    queue: Ref的引用队列,负责存放被引用对象被GC回收了的Ref,主要用于 调用方通过poll()出队判断某个被引用对象是否被回收

    next:指向Ref链表的下一个Ref对象。不同的Ref的状态对应不同的next,   active状态 对应null ,pending状态 对应Ref自己,Enqueued状态 对应队列中的下一个Ref, Inactive状态 对应Ref自己

    discovered:由JVM线程维护,指向下一个要处理的Ref对象。 active状态 对应discovered链表的下一个要处理对象, pending状态 对应pending链表的下一个对象, 其他状态 null

    pending: 由GC线程赋值,等待着ReferenceHandler线程把他加入到队列。这是一个静态对象,意味着所有Reference对象共用同一个pending队列。由discovered字段作为索引指向他的下一个

    lock:内部的一个锁,主要用于并发读写pending时保证线程安全

    两个构造方法

    区别在于是否为Ref创建时指定RefQueue

    重要的一个内部类ReferenceHandler

    这是内部的一个线程,这个线程就是主要负责判断pending是否存在,如果存在是否该入RefQueue

    我们看到这个run()方法中是个while()死循环,不断地执行tryHandlePending()这个方法

    只不过入参是true,这个方法会有某种状态下进度wait()挂起,也就不会执行的太频繁

    初始化时执行的静态代码块

    维护这个RefrenceHandler线程,作为守护线程,执行handler.start()启动线程

    tryHandlePending()方法

    tyr{

      先上锁,这里上锁的目的是为了保证并发读写pending和discovered安全 (有可能你此时在读的同时,GC线程正在写入这个pending)

      if(pending != null) {

        判断是否是Cleaner类型 并赋值给c  (这个Cleaner类型之后在虚引用中会用到)

        pending指针指向discovered,discovered指向null

      } else {

        如果入参为true,执行wait()等待唤醒  (有GC线程写入pending后唤醒)

      }
      

       if(c != null) {

        如果Cleaner不为null
        执行c.clean()

      }

       判断队列并执行入队操作

    }

    验证弱引用

     GC后,数组并没有被回收掉 

    (注意:System.gc()仅仅是建议JVM执行GC,实际上也可能并没有执行)

    虚引用怎么玩

     

    虚引用的源码非常简单

    他暴露的get()方法永远返回null,也就是说你不可能通过虚引用拿到被引用的对象

    构造方法必须传一个引用队列,也就是要通过这个引用队列搞文章

    先看下ReferenceQueue源码

    很简单的一个队列类型,

    维护了两个静态实例,

    维护了一个锁

    维护了一个队列头节点

    剩下的方法就是普通的入队出队方法。

    结合上面分析的RefrenceHandler线程,那么虚引用的玩法就是:

    我为对象a创建了一个虚引用Ref,指定引用队列RefQueue,当对象a被GC回收时,Ref就会被加入到引用队列RefQueue,我可以通过RefQueue.poll()方法来判断对象a是否被回收。

    看下demo

    制定一个引用队列,创建一个狮子狗对象,

    对狮子狗创建虚引用

    当发生GC回收后 看下结果

    引用队列中空空如也,和之前说的不一样啊 为啥呢

    因为狮子狗还有个强引用啊,在栈空间中引用着呢。

    改进一下

    now  狮子狗被GC回收了,虚引用Ref进入队列了,打印出来了,证明了之前的结论

    But

    此时的狮子狗对象真的被GC回收了吗? ? ?

    创建引用队列RefQueue,创建一个引用对象List

    循环创建一万个狮子狗,一万个虚引用 并加入到List

    执行GC

    打上断点,此时我们看

    RefQueue中确实加入了虚引用对象,但是是9999个  (最后一个虚引用并没有入队。原因我还没找到..)

    但是我们打开这个List看下

    狮子狗1到狮子狗10000 一只都不少,都搁这趴着呢

    得出结论,被虚引用的对象,理论上回收级别相当于强引用,没有其他强引用,就会被GC回收,然后Ref对象入队RefQueue

    但实际上并没有真正的回收,只有当Ref对象也被完全回收后才能回收掉。

    在Java 8以及之前的版本中,在虚引用回收后,虚引用指向的对象才会回收。在Java 9以及更新的版本中,虚引用不会对对象的生存产生任何影响。

    DirectByteBuffer创建的堆外内存如何释放?

    聊聊WeekedHashMap

  • 相关阅读:
    lucene学习-创建索引
    ExtJs学习-搭建开发环境
    Struts2上传文件(1)
    使用USBWriter做U盘启动盘后U盘在盘中不显示的解决办法(轉載)
    家里旧电脑装了centos7實踐思路
    win7/win10下装centos7双系统(转载)
    美区google play礼品卡,如何正确充值到美区google play余额,并能购买游戏道具
    excel 2016 打开UTF-8编码CSV文件乱码的问题UTF-8编码CSV文件乱码的问题
    python3 writerow CSV文件多一个空行
    python3 UnicodeEncodeError: 'gbk' codec can't encode character 'xa0' in position 4400: illegal multibyte sequence
  • 原文地址:https://www.cnblogs.com/ttaall/p/15523439.html
Copyright © 2020-2023  润新知