JVM_GC_虚引用

特点

  • 虚引用也称为 幽灵引用 或者 幻影引用;
  • 虚引用并不会决定的对象的生命周期,在任何时间都会被回收掉
  • 在使用虚引用的时候,必须和引用队列一起使用,虚引用的 构造器必须传入一个该类型的引用队列

具体细节

  • 一般用来跟踪垃圾回收过程,在对象被垃圾回收时收到一个系统消息。程序可在收到 某个虚引用对象被回收的消息 后做相应的措施。

  • 当垃圾回收其准备回收一个对象时,如果发现这个对象存在虚引用,就会在垃圾回收 将这个对象的虚引用 加入到引用队列中。

  • 可以通过检查引用队列中是否有 相应 的虚引用来判断对象是否被回收。

  • 在其关联的 虚引用出队之前,是不会销毁该对象的。(使用 poll() 方法从应用队列中出队一个虚引用,虚引用出队后,与之关联的对象才可能会被销毁。)

  • 如果一个对象没有强引用和软引用,对于垃圾回收器而言便是可以被清除的,在清除之前,会调用其finalize方法,如果一个对象已经被调用过finalize方法但是还没有被释放,它就变成了一个虚可达对象。

  • 与软引用和弱引用不同,显式使用虚引用可以阻止对象被清除,只有在程序中显式或者隐式移除这个虚引用时,这个已经执行过finalize方法的对象才会被清除。想要显式的移除虚引用的话,只需要将其从引用队列中取出然后扔掉(置为null)即可。

看一个例子感受一下

import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.LinkedList;
import java.util.List;

public class PhantomReferenceTest {
	private static final List<Object> TEST_DATA = new LinkedList<>();
	private static final ReferenceQueue<TestClass> QUEUE = new ReferenceQueue<>();

	public static void main(String[] args) {
		TestClass obj = new TestClass("Test");
		//构建 虚引用 时,必须传入对应的 ReferenceQueue 
		PhantomReference<TestClass> phantomReference = new PhantomReference<>(obj, QUEUE);
		// 该线程不断读取这个虚引用,并不断往列表里插入数据,以促使系统早点进行GC
		new Thread(() -> {
			while (true) {
				TEST_DATA.add(new byte[1024 * 100]);
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
					Thread.currentThread().interrupt();
				}
				System.out.println(phantomReference.get());
			}
		}).start();

		// 这个线程不断读取引用队列,当弱引用指向的对象被回收前,该引用就会被加入到引用队列中
		new Thread(() -> {
			while (true) {
				//一直出队,如果 虚引用没被加入到引用队列中,一直返回 null ,一旦被加入,则出队
				Reference<? extends TestClass> poll = QUEUE.poll();
				if (poll != null) {
					//一旦一出队,这个说明GC刚才已经准备回收它,把它的虚引用加入到了引用队列中,所以就可以拿引用队列来实现收到虚引用对象被回收的通知
					System.out.println("收到通知啦!!");
					System.out.println("--- 虚引用对象被jvm回收了 ---- " + poll);
					System.out.println("--- 回收对象 ---- " + poll.get());
				}
			}
		}).start();

		obj = null;

		try {
			Thread.currentThread().join();
		} catch (InterruptedException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	static class TestClass {
		private String name;

		public TestClass(String name) {
			this.name = name;
		}

		@Override
		public String toString() {
			return "TestClass - " + name;
		}
	}
}

使用的虚拟机设置如下:

-XX:+PrintGC -Xms4m -Xmx4m -Xmn2m

运行结果如下:

[GC (Allocation Failure)  1024K->680K(3584K), 0.0033086 secs]
[GC (Allocation Failure)  1704K->816K(3584K), 0.0021019 secs]
null
...省略几个null的输出
null
[GC (Allocation Failure)  1766K->1612K(3584K), 0.0014849 secs]
null
null
...省略几个null的输出
null
null
[GC (Allocation Failure) -- 2625K->3525K(3584K), 0.0012936 secs]
[Full GC (Ergonomics)  3525K->2492K(3584K), 0.0087692 secs]
收到通知啦!!
--- 虚引用对象被jvm回收了 ---- java.lang.ref.PhantomReference@79c7f1fb
--- 回收对象 ---- null
null
null
null
[Full GC (Ergonomics)  2913K->2893K(3584K), 0.0035069 secs]
null
[Full GC (Ergonomics)  2993K->2993K(3584K), 0.0024395 secs]
[Full GC (Allocation Failure)  2993K->2993K(3584K), 0.0022958 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
	at PhantomReferenceTest.lambda$0(PhantomReferenceTest.java:18)
	at PhantomReferenceTest$$Lambda$1/834600351.run(Unknown Source)
	at java.lang.Thread.run(Unknown Source)

适用场景

  • 使用虚引用的目的就是为了得知对象被GC的时机,所以可以利用虚引用来进行销毁前的一些操作,比如说资源释放等。这个虚引用对于对象而言完全是无感知的,有没有完全一样,但是对于虚引用的使用者而言,就像是待观察的对象的把脉线,可以通过它来观察对象是否已经被回收,从而进行相应的处理。
  • 事实上,虚引用有一个很重要的用途就是用来做堆外内存的释放,DirectByteBuffer就是通过虚引用来实现堆外内存的释放的。
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值