说到Java的java.lang.ref.Reference有四个子类,大家是不是马上想到了强软弱虚? 实际不是,这四个子类是SoftReference、WeakReference、PhantomReference和FinalReference。 四个子类中没有强引用,没什么奇怪的,但是多出来一个FinalReference就有意思了。 正好在研究虚引用,写个例子,看看弱引用、虚引用、FinalReference都有什么区别。
代码中,存在实现了非空finalize()方法的Test类,main方法中构造了一个Test类的对象test,并在test上添加了弱引用和虚引用,关联引用队列queue。 接下来在去除test的强引用后,两次调用System.gc(),观察输出。 同时,有另外一个监视线程随时检查引用队列queue,一旦发现引用就会打印出来。
import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; public class PhantomReferenceTest { public static void main(String[] args) throws Exception { ReferenceQueue<Test> queue = new ReferenceQueue<>(); Thread moniterThread = new Thread(() -> { // 监视线程,随时检查引用队列,一旦发现引用就会打印出来 for (;;) { Reference<? extends Test> ref = queue.poll(); if (ref != null) { System.out.printf("%s加入引用队列%n", ref.getClass().getSimpleName()); } try { Thread.sleep(0); } catch (InterruptedException e) { break; } } }); moniterThread.start(); Test test = new Test(); WeakReference<Test> weakReference = new WeakReference<Test>(test, queue); PhantomReference<Test> phantomReference = new PhantomReference<Test>(test, queue); // 去除强引用 test = null; System.out.println(">> 第一次gc <<"); System.gc(); // 这里等待一段时间,保证引用进入队列,和finalize()方法执行 Thread.sleep(100); System.out.println("\n>> 第二次gc <<"); System.gc(); assert weakReference != null && phantomReference != null; moniterThread.interrupt(); } public static class Test { @Override protected void finalize() throws Throwable { System.out.println("== finalize() =="); } } }运行时添加了-XX:+PrintGC参数,输出结果如下:
>> 第一次gc << [GC (System.gc()) 3335K->848K(125952K), 0.0824715 secs] [Full GC (System.gc()) 848K->749K(125952K), 0.0100622 secs] WeakReference加入引用队列 == finalize() == >> 第二次gc << [GC (System.gc()) 3411K->1013K(125952K), 0.0009536 secs] [Full GC (System.gc()) 1013K->937K(125952K), 0.0148106 secs] PhantomReference加入引用队列从输出结果中我们能得到如下信息:
第一次gc,对象的finalize()方法被执行,弱引用进入队列。这两个动作不确定顺序。第二次gc,虚引用进入队列。接下来详细解释一下代码执行过程中航都发生了什么:
test对象创建。因为test对象具有非空的finalize方法,所以在test对象的初始化过程中有个特殊的步骤,就是把这个对象包装成一个java.lang.ref.Finalizer塞到Finalizer类的静态链表unfinalized中。给test对象添加弱引用和虚引用,并执行test = null;,此时test对象没有强引用,只有弱引用和虚引用,满足了被垃圾回收的条件。触发gc。jvm准备把test对象回收掉之前,会把它存在的弱引用对象添加到关联的引用队列中。但是,又因为test对象具有finalize方法,所以test对象不会被回收,而是把它的Finalizer添加到Finalizer类的静态引用队列queue中。我们代码中有一个线程一直监视着我们的引用队列,Finalizer的代码中也启动过一个java.lang.ref.Finalizer.FinalizerThread线程一直监视着Finalizer的引用队列。我们的监视线程发现队列变化,就打印出“WeakReference加入引用队列”;FinalizerThread发现队列变化,就执行test对象的finalize方法,打印出“== finalize() ==”。这两个线程说不定谁先发现自己的队列发生变化,因此上面的输出信息顺序不确定。第二次触发gc,这次test对象直接被回收掉,之后把关联的虚引用加入队列中,被监视线程监视到,打印出“PhantomReference加入引用队列”。很多时候,我们认为一个对象的finalize()方法执行过以后,如果对象没有自救,这个对象马上就被垃圾回收了。但是实际不然,有个时间差,要到下次垃圾回收时才会真正回收掉这个对象。
另外,请避免使用finalize()方法。
那么,弱引用和虚引用的区别?
只要jvm有回收掉一个对象的意愿,不管最终有没有回收掉,都会在回收对象之前将弱引用加入到引用队列。 而只有在jvm真正回收掉对象之后,才会把虚引用加入引用队列。