玩转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