Reference 是一个抽象类,而 SoftReference,WeakReference,PhantomReference 以及 FinalReference 都是继承它的具体类。
接下来我们来分别介绍和分析强引用以及 java.lang.ref 包下各种虚引用的特性及用法。
StrongReference, SoftReference, WeakReference 以及 PhantomReference 的特性及用法
StrongReference:
我们都知道 JVM 中对象是被分配在堆(heap)上的,当程序行动中不再有引用指向这个对象时,这个对象就可以被垃圾回收器所回收。这里所说的引用也就是我们一般意义上申明的对象类型的变量(如 String, Object, ArrayList 等),区别于原始数据类型的变量(如 int, short, long 等)也称为强引用。
在了解虚引用之前,我们一般都是使用强引用来对对象进行引用。如:
1. StrongReference usage
- String tag = new String("T");
此处的 tag 引用就称之为强引用。而强引用有以下特征:
强引用可以直接访问目标对象。
强引用所指向的对象在任何时候都不会被系统回收。
强引用可能导致内存泄漏。
我们要讨论的这三种 Reference 较之于强引用而言都属于“弱引用”,也就是他们所引用的对象只要没有强引用,就会根据条件被 JVM 的垃圾回收器所回收,它们被回收的时机以及用法各不相同。下面分别来进行讨论。
SoftReference:
SoftReference 在“弱引用”中属于最强的引用。SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,垃圾回收器会根据 JVM 内存的使用情况(内存的紧缺程度)以及 SoftReference 的 get() 方法的调用情况来决定是否对其进行回收。(后面章节会用几个实验进行阐述)
具体使用一般是通过 SoftReference 的构造方法,将需要用弱引用来指向的对象包装起来。当需要使用的时候,调用 SoftReference 的 get() 方法来获取。当对象未被回收时 SoftReference 的 get() 方法会返回该对象的强引用。如下:
2. SoftReference usage
- SoftReference<Bean> bean = new SoftReference<Bean>(new Bean("name", 10));
- System.out.println(bean.get());
软引用有以下特征:
软引用使用 get() 方法取得对象的强引用从而访问目标对象。
软引用所指向的对象按照 JVM 的使用情况(Heap 内存是否临近阈值)来决定是否回收。
软引用可以避免 Heap 内存不足所导致的异常。
当垃圾回收器决定对其回收时,会先清空它的 SoftReference,也就是说 SoftReference 的 get() 方法将会返回 null,然后再调用对象的 finalize() 方法,并在下一轮 GC 中对其真正进行回收。
WeakReference:
WeakReference 是弱于 SoftReference 的引用类型。弱引用的特性和基本与软引用相似,区别就在于弱引用所指向的对象只要进行系统垃圾回收,不管内存使用情况如何,永远对其进行回收(get() 方法返回 null)。
完全可以通过和 SoftReference 一样的方式来操作 WeakReference,这里就不再复述。
弱引用有以下特征:
弱引用使用 get() 方法取得对象的强引用从而访问目标对象。
一旦系统内存回收,无论内存是否紧张,弱引用指向的对象都会被回收。
弱引用也可以避免 Heap 内存不足所导致的异常。
PhantomReference:
PhantomReference 是所有“弱引用”中最弱的引用类型。不同于软引用和弱引用,虚引用无法通过 get() 方法来取得目标对象的强引用从而使用目标对象,观察源码可以发现 get() 被重写为永远返回 null。
那虚引用到底有什么作用?其实虚引用主要被用来 跟踪对象被垃圾回收的状态,通过查看引用队列中是否包含对象所对应的虚引用来判断它是否 即将被垃圾回收,从而采取行动。它并不被期待用来取得目标对象的引用,而目标对象被回收前,它的引用会被放入一个 ReferenceQueue 对象中,从而达到跟踪对象垃圾回收的作用。
所以具体用法和之前两个有所不同,它必须传入一个 ReferenceQueue 对象。当虚引用所引用对象被垃圾回收后,虚引用会被添加到这个队列中。如:
3. PhantomReference usage
- public static void main(String[] args) {
- ReferenceQueue<String> refQueue = new ReferenceQueue<String>();
- PhantomReference<String> referent = new PhantomReference<String>(
- new String("T"), refQueue);
- System.out.println(referent.get());
-
- System.gc();
- System.runFinalization();
-
- System.out.println(refQueue.poll() == referent);
- }
值得注意的是,对于引用回收方面,虚引用类似强引用不会自动根据内存情况自动对目标对象回收,Client 需要自己对其进行处理以防 Heap 内存不足异常。
虚引用有以下特征:
虚引用永远无法使用 get() 方法取得对象的强引用从而访问目标对象。
虚引用所指向的对象在被系统内存回收前,虚引用自身会被放入 ReferenceQueue 对象中从而跟踪对象垃圾回收。
虚引用不会根据内存情况自动回收目标对象。
另外值得注意的是,其实 SoftReference, WeakReference 以及 PhantomReference 的构造函数都可以接收一个 ReferenceQueue 对象。当 SoftReference 以及 WeakReference 被清空的同时,也就是 Java 垃圾回收器准备对它们所指向的对象进行回收时,调用对象的 finalize() 方法之前,它们自身会被加入到这个 ReferenceQueue 对象中,此时可以通过 ReferenceQueue 的 poll() 方法取到它们。而 PhantomReference 只有当 Java 垃圾回收器对其所指向的对象真正进行回收时,会将其加入到这个 ReferenceQueue 对象中,这样就可以追综对象的销毁情况。
让我们来回顾一下四种引用类型的表现以及在垃圾回收器回收清理内存时的表现 .
软引用 (SoftReference), 引用类型表现为当内存接近满负荷 , 或对象由 SoftReference.get() 方法的调用没有发生一段时间后 , 垃圾回收器将会清理该对象 . 在运行对象的 finalize 方法前 , 会将软引用对象加入 ReferenceQueue 中去 .
弱引用 (WeakReference), 引用类型表现为当系统垃圾回收器开始回收时 , 则立即会回收该对象的引用 . 与软引用一样 , 弱引用也会在运行对象的 finalize 方法之前将弱引用对象加入 ReferenceQueue.
强引用 (FinalReference), 这是最常用的引用类型 . JVM 系统采用 Finalizer 来管理每个强引用对象 , 并将其被标记要清理时加入 ReferenceQueue, 并逐一调用该对象的 finalize() 方法 .
虚引用 (PhantomReference), 这是一个最虚幻的引用类型 . 无论是从哪里都无法再次返回被虚引用所引用的对象 . 虚引用在系统垃圾回收器开始回收对象时 , 将直接调用 finalize() 方法 , 但不会立即将其加入回收队列 . 只有在真正对象被 GC 清除时 , 才会将其加入 Reference 队列中去 .
当多次运行系统垃圾回收后,IBM JVM 将软引用一并加入了回收队列中,并运行了其 finalize 方法。另外,即使经过很多次系统垃圾回收,虚引用也没有被加入到队列中去。不知道这是不是 IBM JVM 的一个小小的 BUG 所在。
SoftReference 中 Oracle JVM 的表现满足规范,只当内存不足时才进行回收。而 IBM JVM 的策略则更为积极,在内存尚且充足的情况下也进行了回收,值得注意。
PhantomReference 中 Oracle JVM 的表现满足规范,执行 finalize 后若干次 GC 就被添加到了 Queue 中。而 IBM JVM 则始终没有被添加到 Queue 中导致了死循环。所以在使用 PhantomReference 时出现类似的情况时,可以考虑是否是因为使用了不同 JVM 所导致。
SoftReference、Weak Reference和PhantomRefrence分析和比较
本文将谈一下对SoftReference(软引用)、WeakReference(弱引用)和PhantomRefrence(虚引用)的理解,这三个类是对heap中java对象的应用,通过这个三个类可以和gc做简单的交互。
强引用:
除了上面提到的三个引用之外,还有一个引用,也就是最长用到的那就是强引用.例如:
Java代码 1.Object o=new Object(); 2.Object o1=o; |
上面代码中第一句是在heap堆中创建新的Object对象通过o引用这个对象,第二句是通过o建立o1到new Object()这个heap堆中的对象的引用,这两个引用都是强引用.只要存在对heap中对象的引用,gc就不会收集该对象.如果通过如下代码:
Java代码 1.o=null; 2.o1=null; |
如果显式地设置o和o1为null,或超出范围,则gc认为该对象不存在引用,这时就可以收集它了。可以收集并不等于就一会被收集,什么时候收集这要取决于gc的算法,这要就带来很多不确定性。例如你就想指定一个对象,希望下次gc运行时把它收集了,那就没办法了,有了其他的三种引用就可以做到了。其他三种引用在不妨碍gc收集的情况下,可以做简单的交互。
heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪种可及的对象,由他的最强的引用决定。如下:
Java代码 1.String abc=new String("abc"); //1 2.SoftReference<String> abcSoftRef=new SoftReference<String>(abc); //2 3.WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3 4.abc=null; //4 5.abcSoftRef.clear();//5 |
上面的代码中:
第一行在heap对中创建内容为“abc”的对象,并建立abc到该对象的强引用,该对象是强可及的。
第二行和第三行分别建立对heap中对象的软引用和弱引用,此时heap中的对象仍是强可及的。
第四行之后heap中对象不再是强可及的,变成软可及的。同样第五行执行之后变成弱可及的。
SoftReference(软引用)
软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足之前会清除所有的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。什么时候会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用是执行以下过程,以上面的abcSoftRef为例:
1 首先将abcSoftRef的referent设置为null,不再引用heap中的new String("abc")对象。
2 将heap中的new String("abc")对象设置为可结束的(finalizable)。
3 当heap中的new String("abc")对象的finalize()方法被运行而且该对象占用的内存被释放, abcSoftRef被添加到它的ReferenceQueue中。
注:对ReferenceQueue软引用和弱引用可以有可无,但是虚引用必须有,参见:
Java代码 Reference(T paramT, ReferenceQueue<? super T>paramReferenceQueue) |
被 Soft Reference 指到的对象,即使没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且没有 Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来SoftReference 不但可以把对象 cache 起来,也不会造成内存不足的错误 (OutOfMemoryError)。我觉得Soft Reference 也适合拿来实作 pooling 的技巧。
A obj = new A(); SoftRefenrence sr = new SoftReference(obj); 引用时 if(sr!=null){ obj = sr.get(); }else{ obj = new A(); sr = new SoftReference(obj); } |
弱引用
当gc碰到弱可及对象,并释放abcWeakRef的引用,收集该对象。但是gc可能需要对此运用才能找到该弱可及对象。通过如下代码可以了明了的看出它的作用:
Java代码 1.String abc=new String("abc"); 2.WeakReference<String> abcWeakRef = new WeakReference<String>(abc); 3.abc=null; 4.System.out.println("before gc: "+abcWeakRef.get()); 5.System.gc(); 6.System.out.println("after gc: "+abcWeakRef.get()); |
运行结果:
before gc: abc
after gc: null
gc收集弱可及对象的执行过程和软可及一样,只是gc不会根据内存情况来决定是不是收集该对象。
如果你希望能随时取得某对象的信息,但又不想影响此对象的垃圾收集,那么你应该用 Weak Reference 来记住此对象,而不是用一般的 reference。
A obj = new A(); WeakReference wr = new WeakReference(obj); obj = null; //等待一段时间,obj对象就会被垃圾回收 ... if (wr.get()==null) { System.out.println("obj 已经被清除了 "); } else { System.out.println("obj 尚未被清除,其信息是 "+obj.toString()); } ... } |
在此例中,透过 get() 可以取得此 Reference 的所指到的对象,如果返回值为 null 的话,代表此对象已经被清除。
这类的技巧,在设计 Optimizer 或 Debugger 这类的程序时常会用到,因为这类程序需要取得某对象的信息,但是不可以 影响此对象的垃圾收集。
PhantomRefrence(虚引用)
虚顾名思义就是没有的意思,建立虚引用之后通过get方法返回结果始终为null,通过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null.先看一下和gc交互的过程在说一下他的作用.
1 不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable).
2 与软引用和弱引用不同, 先把PhantomRefrence对象添加到它的ReferenceQueue中.然后在释放虚可及的对象.
你会发现在收集heap中的new String("abc")对象之前,你就可以做一些其他的事情.通过以下代码可以了解他的作用.
Java代码 1.import java.lang.ref.PhantomReference; 2.import java.lang.ref.Reference; 3.import java.lang.ref.ReferenceQueue; 4.import java.lang.reflect.Field; 5. 6.public class Test { 7. public static boolean isRun = true; 8. 9. public static void main(String[] args) throws Exception { 10. String abc = new String("abc"); 11. System.out.println(abc.getClass() + "@" + abc.hashCode()); 12. final ReferenceQueue referenceQueue = new ReferenceQueue<String>(); 13. new Thread() { 14. public void run() { 15. while (isRun) { 16. Object o = referenceQueue.poll(); 17. if (o != null) { 18. try { 19. Field rereferent = Reference.class 20. .getDeclaredField("referent"); 21. rereferent.setAccessible(true); 22. Object result = rereferent.get(o); 23. System.out.println("gc will collect:" 24. + result.getClass() + "@" 25. + result.hashCode()); 26. } catch (Exception e) { 27. 28. e.printStackTrace(); 29. } 30. } 31. } 32. } 33. }.start(); 34. PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc, 35. referenceQueue); 36. abc = null; 37. Thread.currentThread().sleep(3000); 38. System.gc(); 39. Thread.currentThread().sleep(3000); 40. isRun = false; 41. } 42. 43.} |
结果为
class java.lang.String@96354
gc will collect:class java.lang.String@96354