原创

Java引用类型之弱引用与幻像引用

这一篇将介绍弱引用和幻像引用。

1、WeakReference

WeakReference也就是弱引用,弱引用和软引用类似,它是用来描述"非必须"的对象的,它的强度比软引用要更弱一些。被弱引用关联的对象只能生存到下一次垃圾收集发生之前,简言之就是:一旦发生GC必定回收被弱引用关联的对象,不管当前的内存是否足够。WeakReference类的定义如下:

public class WeakReference<T> extends Reference<T> {    
    public WeakReference(T referent) {  
        super(referent);    
    }   
    public WeakReference(T referent, ReferenceQueue<? super T> q) {   
        super(referent, q); 
    }   
}

WeakReference继承了Reference。在ReferenceProcessorStats ReferenceProcessor::process_discovered_references()方法中调用process_discovered_reflist()方法处理弱引用,如下:


// Weak references
size_t weak_count = 0;
{
    // 传递的clear_referent的值为true
    weak_count =  process_discovered_reflist(_discoveredWeakRefs, NULL, true,
                                 is_alive, keep_alive, complete_gc, task_executor);
}

调用的process_discovered_reflist()方法中会用后2个阶段处理弱引用,在之前已经介绍过,这里不再介绍。

2、PhantomReference

PhantomReference也就是幻像引用,它是所有引用类型中最弱的一种。一个对象是否关联到虚引用,完全不会影响该对象的生命周期,也无法通过虚引用来获取一个对象的实例。为对象设置一个虚引用的唯一目的是:能在此对象被垃圾收集器回收的时候收到一个系统通知。PhantomReference类的定义如下:

public class PhantomReference<T> extends Reference<T> { 
    public T get() {    
        return null;    
    }   
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {    
        super(referent, q); 
    }   
}

可以看到幻像引用的get()方法永远返回null,所以也就无法通过虚引用来获取一个对象的实例。

public static void demo() throws InterruptedException { 
        Object                    obj = new Object();   
        ReferenceQueue<Object>    refQueue =new ReferenceQueue<>(); 
        PhantomReference<Object>  phanRef =new PhantomReference<>(obj, refQueue);   
        Object                    objg = phanRef.get(); 
        // 这里拿到的是null   
        System.out.println(objg);   
        // 让obj变成垃圾 
        obj=null;   
        System.gc();    
        Thread.sleep(3000); 
        // GC后会将phanRef加入到refQueue中 
        Reference<? extends Object> phanRefP = refQueue.remove(); 
         //这里输出true 
        System.out.println(phanRefP==phanRef);  
}

从以上代码中可以看到,幻像引用能够在指向对象不可达时得到一个“通知”(其实所有继承Reference的类都有这个功能),需要注意的是GC完成后,phanRef.referent依然指向之前创建Object,也就是说Object对象一直没被回收!

而造成这一现象的原因在之前也介绍过: 对于Finalreference和Phantomreference来说,clear_referent 字段传入的值为false,意味着被这两种引用类型引用的对象,如果没有其他额外处理,在GC中是不会被回收的。

对于幻像引用来说,从refQueue.remove()得到引用对象后,可以调用clear()方法强行解除引用和对象之间的关系,使得对象下次可以GC时可以被回收掉。
在ReferenceProcessor::process_discovered_references()方法中调用process_discovered_reflist()方法处理幻像引用,如下:

// Phantom references
size_t phantom_count = 0;
{
    // 传递的clear_referent的值为false
    phantom_count =  process_discovered_reflist(_discoveredPhantomRefs, NULL, false,
                                 is_alive, keep_alive, complete_gc, task_executor);
}

调用的process_discovered_reflist()方法中会用后2个阶段处理幻像引用,在之前已经介绍过,这里不再介绍。

3、Cleaner

PhantomReference有两个比较常用的子类,如下:

(1)java.lang.ref.Cleaner:开发者用于在引用对象回收的时候触发一个动作,在JDK 9中将完全替代Object.finalize()方法。

(2)jdk.internal.ref.Cleaner:用于DirectByteBuffer对象回收的时候对于堆外内存的回收。ReferenceHandler线程会对pending链表中的jdk.internal.ref.Cleaner类型引用对象调用其clean()方法。

对于java.lang.ref.Cleaner类来说,举个例子如下:


import java.lang.ref.Cleaner;

public class CleaningExample implements AutoCloseable {
    // A cleaner, preferably one shared within a library
    private static final Cleaner cleaner = Cleaner.create();

    static class State implements Runnable {
        State() {
            System.out.println("init");// initialize State needed for cleaning action
        }

        public void run() {
            System.out.println("clean");// cleanup action accessing State, executed at most once
        }
    }

    private final State state;
    private final Cleaner.Cleanable cleanable;

    public CleaningExample() {
        this.state = new State();
        this.cleanable = cleaner.register(this, state);
    }

    public void close() {
        cleanable.clean();
    }

    public static void main(String[] args) {
        while(true) {
            new CleaningExample();
        }
    }
}

本例中每次创建对象时,都会打印init;回收对象时,都会打印clean。  

下面介绍jdk.internal.ref.Cleaner类。

Cleaner继承自Java四大引用类型之一的幻像引用PhantomReference(众所周知,无法通过幻像引用获取与之关联的对象实例,且当对象仅被幻像引用引用时,在任何发生GC的时候,其均可被回收),通常PhantomReference与引用队列ReferenceQueue结合使用,可以实现幻像引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。如下图所示,当某个被Cleaner引用的对象将被回收时,JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中,等待ReferenceHandler进行相关处理。其中,ReferenceHandler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean()方法进行相关清理工作,这在之前介绍ReferenceHandler类时介绍过,这里不再介绍。

file

Cleaner只有在清理逻辑足够轻量和直接的时候才适合使用Cleaner,繁琐耗时的清理逻辑将有可能导致ReferenceHandler线程阻塞从而耽误其它的清理任务。

正文到此结束