玩转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源码分析

先看属性

referent:被引用的对象

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

 

posted @ 2021-11-08 11:28  六小扛把子  阅读(113)  评论(0编辑  收藏  举报