Expunge 删除,抹去
stale 陈腐、老旧的
Reference 引用类
强引用、软引用、弱引用、虚引用
软引用、弱引用、虚引用,可以配合ReferenceQueue实现对象被回收时候的监听
Reference
重要属性:
1. private T referent;
weakHashMap中就是那个Entry的Key Treated specially by GC
2. volatile ReferenceQueue<? super T> queue;
Reference队列,GC后Reference对象会被加到这里面,是放到队列头部。
3. private static Lock lock = new Lock();
GC时候必须要获得这个锁
4. private static Reference<Object> pending = null;
跟虚拟机,GC打交道的。 GC后Reference对象会被GC自动设置到这个引用中,然后由ReferenceHandler线程把他放到ReferenceQueue里面.
5. transient private Reference<T> discovered;
跟虚拟机,GC打交道的 used by VM
6. volatile Reference next;
ReferenceQueue是linkedList类似是实现,链表结构,Reference就相当于其中的Node,有一个next的指针指向下一个对象。
ReferenceHandler 内部类:
- 在Reference类的静态代码块中初始化,然后运行。
- 主要功能是在tryHandlePending()方法中,把Reference类的静态变量pending指向的Reference对象丢到他自己的queue中。
- 于此同时,如果pending所指的Reference对象是Cleaner,那么还会执行Cleaner的clean方法,而且不会放到queue中,执行了clean方法后就返回了。
Cleaner在DirectByteBuffer中有使用,下面介绍.
Cleaner类
public class Cleaner extends PhantomReference<Object>
集成虚引用, 也就是说cleaner的var0只要他强引用消失了,那么var0所指的对象就随时会被GC掉。
//创建方法,add方法也是把cleaner自身构建成一个链表结构
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
private Cleaner(Object var1, Runnable var2) {
super(var1, dummyQueue);
this.thunk = var2;
}
clean方法,在Reference的tryHandlePending方法中会执行,即Cleaner关联的对象被GC放到pending中,然后ReferenceHandler线程执行tryHandlePending时候执行。
public void clean() {
//删除自身节点
if (remove(this)) {
try {
//执行创建时候传进来的runnable对象的run方法,执行相关的业务逻辑
//比如DirectByteBuffer类中,他有个cleaner,run方法是用于释放直接内存
//详见下
this.thunk.run();
} catch (final Throwable var2) {
//省略。。。
}
}
}
Cleaner在DirectByteBuffer中有使用,下面介绍.
DirectByteBuffer中Cleaner的使用
DirectByteBuffer基于unsafe类的allocateMemory来分配直接内存使用。
基于Cleaner来进行直接内存的释放。
直接内存申请,释放流程:
-
new DirectByteBuffer(1024)
构建对象,构造方法内部调用Unsafe.allocateMemory申请到直接内存(不归JVM GC管辖) - 构造方法内部还调用``Cleaner.create(this, new Deallocator(base, size, cap))`把当前DirectByteBuffer设置为虚引用,并设置一个Deallocator的runnable类。
- 当DirectByteBuffer强引用消失,即不可达之后,由于Cleaner是虚引用,不影响GC,所以DirectByteBuffer被GC掉了
- 与此同时,Cleaner对象也被GC设置到Reference的静态属性pending中(这个时候Cleaner即Reference的Referent引用指向的DirectByteBuffer已经是null了)
- Reference中的ReferenceHandler线程执行tryHandlePending,处理pending,即该Cleaner
- tryHandlePending中调用Cleaner的clean方法,clean方法内部调用构建时候传入的runnable对象的run方法,即Deallocator的run方法。
- Deallocator的run方法中调用了
unsafe.freeMemory(address);
来释放DirectByteBuffer在构建时候申请的直接内存 - 以此来完成直接内存的释放
WeakHashMap
https://hongjiang.info/java-referencequeue/
以前设计缓存时也曾过用WeakHashMap来实现,对Java的Reference
稍做过一些了解,其实这个问题,归根到底,是个Java GC
的问题,由垃圾回收器与ReferenceQueue
的交互方式决定的。WeakHashMap
的实现也是通过ReferenceQueue
这个“监听器”来优雅的实现自动删除那些引用不可达的key的。
Entry类继承了WeakReference, 其中的Key是弱引用,并且WeakHashMap中定义了一个ReferenceQueue,用于监听key的回收
//Entry类继承弱引用
private static class Entry<K,V> extends WeakReference<Object> {}
//WeakHashMap中的实例变量用于监听key的回收
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
流程
当WeakHashMap中Key没有其他强引用时候,发生了一次任意GC,由于Entry的Key是弱引用,那么被GC回收。
被GC回收的同时,Entry类(继承了弱引用)会由GC自动放入到Reference的静态属性pending中。
Reference类在类初始化后有个static代码块,里面启动了一个高优先级的daemon线程,用于把pending指向的Reference对象放到ReferenceQueue中
经过3后,Entry就被丢到了ReferenceQueue中
当你在GC后调用WeakHashMap的get、put、size等方法时候,他会调用自己的expungeStaleEntries()方法。
expungeStaleEntries方法会poll出queue中的Entry类,然后把entry类处理下,value=null,释放掉value的强引用,然后处理前后的指针,size--之类。
经过6之后,key对应的value强引用也被抹去了,所以GC也可以把value以及整个entry回收掉。