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类时介绍过,这里不再介绍。 

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

相关文章的链接如下:

1、在Ubuntu 16.04上编译OpenJDK8的源代码 

2、调试HotSpot源代码

3、HotSpot项目结构 

4、HotSpot的启动过程 

5、HotSpot二分模型(1)

6、HotSpot的类模型(2)  

7、HotSpot的类模型(3) 

8、HotSpot的类模型(4)

9、HotSpot的对象模型(5)  

10、HotSpot的对象模型(6) 

11、操作句柄Handle(7)

12、句柄Handle的释放(8)

13、类加载器 

14、类的双亲委派机制 

15、核心类的预装载

16、Java主类的装载  

17、触发类的装载  

18、类文件介绍 

19、文件流 

20、解析Class文件 

21、常量池解析(1) 

22、常量池解析(2)

23、字段解析(1)

24、字段解析之伪共享(2) 

25、字段解析(3)  

26、字段解析之OopMapBlock(4)

27、方法解析之Method与ConstMethod介绍  

28、方法解析

29、klassVtable与klassItable类的介绍  

30、计算vtable的大小 

31、计算itable的大小 

32、解析Class文件之创建InstanceKlass对象 

33、字段解析之字段注入 

34、类的连接  

35、类的连接之验证 

36、类的连接之重写(1) 

37、类的连接之重写(2)

38、方法的连接  

39、初始化vtable 

40、初始化itable  

41、类的初始化 

42、对象的创建  

43、Java引用类型 

44、Java引用类型之软引用(1)

45、Java引用类型之软引用(2)

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

作者持续维护的个人博客  classloading.com

关注公众号,有HotSpot源码剖析系列文章!

    

参考文章:

Java魔法类:Unsafe应用解析 

原文地址:https://www.cnblogs.com/mazhimazhi/p/13504081.html