众所周知,目前Java的引用分为强引用、软引用、弱引用和虚引用,这四种强度依次逐渐减弱。网上也能搜到很多相关文章,有的写的也很好,可能自身理解能力比较差,每次看过之后总觉得差点意思,本章准备通过几个小例子来加深理解。下面先简单看一下四种引用的定义:
这是从《深入理解Java虚拟机》第三版中摘抄出来的,下面就用例子来证明上述定义,同时加深对其的理解。
/** * vm args: -Xmx11m * */ public class StrongReferenceTest{ public static void main(String[] args){ // -Xmx11m,正好够生成a、b两个对象 A a = new A(); B b = new B(a); // 将栈帧中的局部变量a指向null,释放对堆上A对象实例的强引用 // 但是b仍然持有对堆上A对象实例的强引用 a = null; // 再次构造一个A对象,报OOM A newA = new A(); } static class A{ int[] a; public A(){ a = new int[1024 * 1024]; } } static class B{ A a; public B(A a){ this.a = a; } } } Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at cn.didadu.sample.jvm.reference.StrongReferenceTest$A.<init>(StrongReferenceTest.java:29) at cn.didadu.sample.jvm.reference.StrongReferenceTest.main(StrongReferenceTest.java:22)
上述代码的意图是:固定堆的大小正好够创建A、B两个对象的实例,同时B对象强引用A对象。运行过程参照注释,最后当再次想创建一个A对象实例时,因为之前创建的A对象实例仍然被强引用着,此时堆没有空间再创建新对象,所以报出OutOfMemoryError。
/** * vm args: -Xmx11m * */ public class SoftReferenceTest{ public static void main(String[] args){ // -Xmx11m,正好够生成a、b两个对象 A a = new A(); B b = new B(a); // 将栈帧中的局部变量a指向null,释放对堆上A对象实例的强引用 // 这一步之后,意味着堆上A对象实例没有强引用了,只有b对它的软引用 a = null; // 再次构造一个A对象,成功 A newA = new A(); } static class A{ int[] a; public A(){ a = new int[1024 * 1024]; } } static class B{ SoftReference<A> softA; public B(A a){ this.softA = new SoftReference<>(a); } } }
上述代码的意图是:固定堆的大小正好够创建A、B两个对象的实例,同时B对象软引用A对象。运行过程参照注释,最后当再次想创建一个A对象实例时,因为之前创建的A对象实例只有一个软引用与之关联,此时堆没有空间再创建新对象,会触发GC回收A对象实例,所有可以成功再创建出另一个新的A对象实例。
public class WeekReferenceTest{ public static void main(String[] args){ A a = new A(); B b = new B(a); // 将栈帧中的局部变量a指向null,释放对堆上A对象实例的强引用 // 这一步之后,意味着堆上A对象实例没有强引用了,只有b对它的弱引用 a = null; // 执行GC,假设GC能及时响应 System.gc(); // 由于堆上的A对象实例只有弱引用,GC时被垃圾回收了 // 所以,b.getA()将返回null System.out.println(b.getA()); } static class A{ } static class B{ WeakReference<A> weakA; public B(A a){ weakA = new WeakReference<>(a); } public A getA(){ return weakA.get(); } } }
上述代码的意图是:不用再固定堆的大小,默认就好,B对象弱引用A对象,运行过程参照注释,最终输出null,只有弱引用的A对象实例被回收了。
虚引用必须和引用队列(ReferenceQueue)联合使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。