PhantomReference分析

本文介绍了PhantomReference的作用,以及它与WeakReference区别。同时纠正网上关于引用对象回收时间的错误说法(个人认为不正确)。本文没有考虑finalize方法是如何实现(具体可以参考Finalizer和FinalReference的介绍),只考虑finalize方法执行条件和结果。

术语
  • reference(phantom reference):引用对象本身,也就是WeakReference,PhantomReference等对象
  • 清除(clear):引用中的reference字段被设置为null,即切断reference与其引用的对象之间的关系
  • phantom可达对象:对象不是强,软,弱可达,同时被设置为finalized后,对象就是phantom可达
  • finalized状态:对象的finalize方法执行后,对象所处的状态
对象的生命周期

 下面介绍一个phantom reference 引用的对象从创建到被回收会经历哪些事

  1. 创建对象,以及phantom reference
public static TestObject instanceA = null;
ReferenceQueue<TestObject> queue = new ReferenceQueue<>();
instanceA = new TestObject();   // 创建对象
PhantomReference<TestObject> phantomRef = 
				new PhantomReference<TestObject>(instanceA, queue); //创建phantom引用
  1. 对象不可达(这里指没有强引用)
instanceA = null; // 对象已经不需要再使用了,可以被回收了
  1. 对象被GC发现其只有phantom reference引用,检查是否需要执行finalize方法
// GC发现了这个对象可以被回收,如果对象覆盖了finalize方法还需要执行finalize方法
System.gc();

// 对象的finalize方法
protected void finalize() throws Throwable {
	super.finalize();
	System.out.println("finalize method executed");
}

  1. 对象已经处于finalized状态
  2. 对象再次被GC发现,认为它是phantom可达对象,将其加入reference的pending链表
  3. ReferenceHandler将pending链表表头加入ReferenceQueue
  4. 手动清除(clear) phantom reference对象成为完全不可达对象(phantom reference 也没有对象的引用)
  5. GC发现对象不可达,回收对象所占的空间
对象再生

 这里解释下为什么要两次GC才能将对象视为phantom可达对象。因为第一次GC时,finalize方法没有被执行过,所以即使对象已经没有其他引用了,但是仍然不满足phantom可达对象的条件(对象处于finalized状态)。如果对象没有覆盖finalize方法就可以直接视为finalized状态,那么一次GC就可以将对象视为phantom可达对象。第二次GC发现对象没有其他引用,同时又处于finalized状态,那么这次就可以将对象视为phantom可达对象。

 如果第二次GC发现对象有其他引用呢?这里就引申出对象再生问题,为什么对象会再生呢?这种情况只可能发生在finalize方法中将对象赋值给了其他引用(不推荐这种用法),因为能访问到对象的只有其本身(PhantomReference的get方法也访问不到对象,虽然反射可以得到对象的引用但是这不是个好习惯)。如果已经发生了这种意外怎么办?本来是希望让finalize方法作为对象回收时的清理工作(期望对象被回收),但是对象却再生了,很显然不能期望在 finalize 方法中进行资源释放

 为了应对上面finalize的缺陷,phantom reference 派上了用场。当然这也不是单靠phantom reference就可以完成的,它也利用了finalize方法只能执行一次的特点,也就是说对象只可能再生一次。那么当GC再次发现对象不可达之后,它之前就处于finalized状态了,这时就满足了phantom可达对象的条件。然后GC就会将phantom reference 添加到pending链表,以便ReferenceHandler将其放入队列。

其他问题

 网上有人认为对象有几种状态Unfinalized Finalizable Finalized Reclaimed,那么这几种分别对应上面对象的哪个生命阶段呢?

  • Unfinalized: 对象被创建后,而且有比phantom reference更强的引用。
  • Finalizable: GC发现对象只有phantom reference引用后,且其finalize方法没有执行。
  • Finalized:无论对象有没有引用,只要对象的fanalize方法被执行,对象处于Finalized状态
  • Reclaimed: GC释放了对象占用的内存

 网上有人说weak reference要等到对象被释放后才能入队,而phantom reference在对象被释放之前就入队了,这个说法有没有问题?

  • 首先,WeakReferece的注释中并没有强调weak reference引用的对象什么时候被回收,只说过weak reference以及与它有关的一系列weak reference 都会被清除(clear)后,weak reference才会入队。
  • 其次,通过设置断点,可以观察在执行一次 GC 后, Reference 的 pending 链表中存在 Finalizer 引用和 WeakReference 引用。我们知道 有一个 ReferenceHandler 线程负责将 pending 队列中的reference 加入对应的队列中,将 Finalizer 加入到 Finalizer 的 f-queue 中等待执行 finalize 方法,而 WeakReference 会被加入到 用户指定的队列中。因此,WeakReference 的入队和对象被释放没有关系,但是由于 WeakReference 的 clear 方法已经被调用,如果在 finalize 方法不能将对象再生, 那么这个对象相当于被回收了。
  • 最后,根据上面对象的生命周期phantom reference引用的对象一定没有被回收。这一点在PhantomReference的注释中也提到过,phantom reference引用的对象会一直存在,直到phantom reference被清除(clear)或者phantom reference本身也成为不可达对象。
参考代码
public class TestObject{
	public static TestObject instanceA = null;
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		System.out.println("finalize method executed");
	}
	
	public static void main(String[] args) 
			throws InterruptedException {
		ReferenceQueue<TestObject> queue = new ReferenceQueue<>();
		instanceA = new TestObject(); //创建对象
		/*WeakReference<TestObject> weakRef = 
		new WeakReference<TestObject>(instanceA, queue); */
		PhantomReference<TestObject> phantomRef = 
				new PhantomReference<TestObject>(instanceA, queue); //创建phantom引用
		instanceA = null; // 不再使用对象
		System.gc();
		
		/*
		 * 将pending链表节点加入queue的ReferenceHandler是并发执行的,
		 * 等待其将reference放入queue。另外,执行finalize方法的
		 * FinalizerThread线程优先级很低,需要等finalize方法执行。
		 */
		Thread.sleep(1000);	
		System.gc();
		System.out.println(queue.poll());
	}
}
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PhantomReference(幽灵引用)是 Java 中的一种引用类型,它是弱引用(WeakReference)的一种变种。与弱引用不同的是,当一个对象只剩下幽灵引用时,它在下一次垃圾回收时就会被回收,并且在回收之前,幽灵引用对象会被放入一个队列中,以便进行一些处理操作。 PhantomReference 可以用于实现一些特殊的功能,例如在对象被回收时进行一些资源释放或清理操作。通常,我们可以通过继承 PhantomReference 类或使用它的包装类 PhantomReference<T> 来创建幽灵引用对象。 下面是一个使用幽灵引用的示例代码: ``` import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; public class ResourceCleaner extends Thread { private ReferenceQueue<Resource> queue; public ResourceCleaner() { this.queue = new ReferenceQueue<>(); setDaemon(true); start(); } public void register(Resource resource) { PhantomReference<Resource> ref = new PhantomReference<>(resource, queue); // 注册幽灵引用对象 } @Override public void run() { try { while (true) { PhantomReference<Resource> ref = (PhantomReference<Resource>) queue.remove(); // 从队列中取出幽灵引用对象 Resource resource = ref.get(); if (resource != null) { // 执行资源释放或清理操作 resource.release(); } ref.clear(); } } catch (InterruptedException ex) { // 终止线程 } } } ``` 在上述代码中,我们创建了一个 ResourceCleaner 类,它继承自 Thread 类,并重写了其 run() 方法。在 ResourceCleaner 类中,我们定义了一个 ReferenceQueue<Resource> 对象,用于存放幽灵引用对象。在 register() 方法中,我们创建了一个幽灵引用对象,并将其注册到队列中。当对象被回收时,幽灵引用对象就会被放入队列中,从而触发 ResourceCleaner 线程的执行。在 ResourceCleaner 线程中,我们可以从队列中取出幽灵引用对象,并执行资源释放或清理操作。需要注意的是,在执行完资源释放或清理操作之后,我们还需要调用幽灵引用对象的 clear() 方法来清除它,以便让 JVM 可以正常回收幽灵引用对象本身。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值