JAVA引用(WeakHashMap、Cleaner)

Expunge 删除,抹去
stale   陈腐、老旧的

Reference 引用类

强引用、软引用、弱引用、虚引用

软引用、弱引用、虚引用,可以配合ReferenceQueue实现对象被回收时候的监听

Reference

重要属性:

1. private T referent;       
weakHashMap中就是那个Entry的Key   Treated specially by GC

2. volatile ReferenceQueue queue;
Reference队列,GC后Reference对象会被加到这里面,是放到队列头部。

3. private static Lock lock = new Lock();
GC时候必须要获得这个锁

4. private static Reference pending = null;
跟虚拟机,GC打交道的。 GC后Reference对象会被GC自动设置到这个引用中,然后由ReferenceHandler线程把他放到ReferenceQueue里面.
    
5. transient private Reference discovered; 
跟虚拟机,GC打交道的  used by VM 

6. volatile Reference next;
ReferenceQueue是linkedList类似是实现,链表结构,Reference就相当于其中的Node,有一个next的指针指向下一个对象。

ReferenceHandler 内部类:

  1. 在Reference类的静态代码块中初始化,然后运行。
  2. 主要功能是在tryHandlePending()方法中,把Reference类的静态变量pending指向的Reference对象丢到他自己的queue中。
  3. 于此同时,如果pending所指的Reference对象是Cleaner,那么还会执行Cleaner的clean方法,而且不会放到queue中,执行了clean方法后就返回了。

Cleaner在DirectByteBuffer中有使用,下面介绍.

Cleaner类

public class Cleaner extends PhantomReference 集成虚引用, 也就是说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来进行直接内存的释放。

直接内存申请,释放流程:
  1. new DirectByteBuffer(1024)构建对象,构造方法内部调用Unsafe.allocateMemory申请到直接内存(不归JVM GC管辖)
  2. 构造方法内部还调用``Cleaner.create(this, new Deallocator(base, size, cap))`把当前DirectByteBuffer设置为虚引用,并设置一个Deallocator的runnable类。
  3. 当DirectByteBuffer强引用消失,即不可达之后,由于Cleaner是虚引用,不影响GC,所以DirectByteBuffer被GC掉了
  4. 与此同时,Cleaner对象也被GC设置到Reference的静态属性pending中(这个时候Cleaner即Reference的Referent引用指向的DirectByteBuffer已经是null了)
  5. Reference中的ReferenceHandler线程执行tryHandlePending,处理pending,即该Cleaner
  6. tryHandlePending中调用Cleaner的clean方法,clean方法内部调用构建时候传入的runnable对象的run方法,即Deallocator的run方法。
  7. Deallocator的run方法中调用了unsafe.freeMemory(address);来释放DirectByteBuffer在构建时候申请的直接内存
  8. 以此来完成直接内存的释放

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 extends WeakReference {}
  
  //WeakHashMap中的实例变量用于监听key的回收
  private final ReferenceQueue queue = new ReferenceQueue<>();

流程
  1. 当WeakHashMap中Key没有其他强引用时候,发生了一次任意GC,由于Entry的Key是弱引用,那么被GC回收。

  2. 被GC回收的同时,Entry类(继承了弱引用)会由GC自动放入到Reference的静态属性pending中。

  3. Reference类在类初始化后有个static代码块,里面启动了一个高优先级的daemon线程,用于把pending指向的Reference对象放到ReferenceQueue中

  4. 经过3后,Entry就被丢到了ReferenceQueue中

  5. 当你在GC后调用WeakHashMap的get、put、size等方法时候,他会调用自己的expungeStaleEntries()方法。

  6. expungeStaleEntries方法会poll出queue中的Entry类,然后把entry类处理下,value=null,释放掉value的强引用,然后处理前后的指针,size--之类。

  7. 经过6之后,key对应的value强引用也被抹去了,所以GC也可以把value以及整个entry回收掉。

你可能感兴趣的:(JAVA引用(WeakHashMap、Cleaner))