弱引用、虚引用、finalize实践,及它们的顺序

来源:互联网 发布:网络吃火锅是什么意思 编辑:程序博客网 时间:2024/06/05 11:28

说到Java的java.lang.ref.Reference有四个子类,大家是不是马上想到了强软弱虚?
实际不是,这四个子类是SoftReferenceWeakReferencePhantomReferenceFinalReference
四个子类中没有强引用,没什么奇怪的,但是多出来一个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加入引用队列

从输出结果中我们能得到如下信息:

  1. 第一次gc,对象的finalize()方法被执行,弱引用进入队列。这两个动作不确定顺序。
  2. 第二次gc,虚引用进入队列。

接下来详细解释一下代码执行过程中航都发生了什么:

  1. test对象创建。因为test对象具有非空的finalize方法,所以在test对象的初始化过程中有个特殊的步骤,就是把这个对象包装成一个java.lang.ref.Finalizer塞到Finalizer类的静态链表unfinalized中。
  2. 给test对象添加弱引用和虚引用,并执行test = null;,此时test对象没有强引用,只有弱引用和虚引用,满足了被垃圾回收的条件。
  3. 触发gc。jvm准备把test对象回收掉之前,会把它存在的弱引用对象添加到关联的引用队列中。但是,又因为test对象具有finalize方法,所以test对象不会被回收,而是把它的Finalizer添加到Finalizer类的静态引用队列queue中。
  4. 我们代码中有一个线程一直监视着我们的引用队列,Finalizer的代码中也启动过一个java.lang.ref.Finalizer.FinalizerThread线程一直监视着Finalizer的引用队列。我们的监视线程发现队列变化,就打印出“WeakReference加入引用队列”;FinalizerThread发现队列变化,就执行test对象的finalize方法,打印出“== finalize() ==”。这两个线程说不定谁先发现自己的队列发生变化,因此上面的输出信息顺序不确定。
  5. 第二次触发gc,这次test对象直接被回收掉,之后把关联的虚引用加入队列中,被监视线程监视到,打印出“PhantomReference加入引用队列”。

结论

很多时候,我们认为一个对象的finalize()方法执行过以后,如果对象没有自救,这个对象马上就被垃圾回收了。但是实际不然,有个时间差,要到下次垃圾回收时才会真正回收掉这个对象。

另外,请避免使用finalize()方法

那么,弱引用和虚引用的区别?

只要jvm有回收掉一个对象的意愿,不管最终有没有回收掉,都会在回收对象之前将弱引用加入到引用队列。
而只有在jvm真正回收掉对象之后,才会把虚引用加入引用队列。

0 0