SoftReference、Weak Reference和PhantomRefrence分析和比較
本文將談一下對SoftReference(軟引用)、WeakReference(弱引用)和PhantomRefrence(虛引用)的理解,這三個類是對heap中java對象的應用,通過這個三個類可以和gc做簡單的交互。
強引用:
除了上面提到的三個引用之外,還有一個引用,也就是最長用到的那就是強引用。例如:
Object o=new Object(); Object o1=o; |
上面代碼中第一句是在heap堆中創建新的Object對象通過o引用這個對象,第二句是通過o建立o1到new Object()這個heap堆中的對象的引用,這兩個引用都是強引用.只要存在對heap中對象的引用,gc就不會收集該對象.如果通過如下代碼:
o=null; o1=null; |
如果顯式地設置o和o1為null,或超出范圍,則gc認為該對象不存在引用,這時就可以收集它了??梢允占⒉坏扔诰鸵粫皇占?,什么時候收集這要取決于gc的算法,這要就帶來很多不確定性。例如你就想指定一個對象,希望下次gc運行時把它收集了,那就沒辦法了,有了其他的三種引用就可以做到了。其他三種引用在不妨礙gc收集的情況下,可以做簡單的交互。
heap中對象有強可及對象、軟可及對象、弱可及對象、虛可及對象和不可到達對象。應用的強弱順序是強、軟、弱、和虛。對于對象是屬于哪種可及的對象,由他的最強的引用決定。如下:
String abc=new String("abc"); //1 SoftReference<String> abcSoftRef=new SoftReference<String>(abc); //2 WeakReference<String> abcWeakRef = new WeakReference<String>(abc); //3 abc=null; //4 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軟引用和弱引用可以有可無,但是虛引用必須有,參見:
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(); //引用時 |
弱引用
當gc碰到弱可及對象,并釋放abcWeakRef的引用,收集該對象。但是gc可能需要對此運用才能找到該弱可及對象。通過如下代碼可以了明了的看出它的作用:
String abc=new String("abc"); WeakReference<String> abcWeakRef = new WeakReference<String>(abc); abc=null; System.out.println("before gc: "+abcWeakRef.get()); System.gc(); 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) { |
在此例中,透過 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")對象之前,你就可以做一些其他的事情。通過以下代碼可以了解他的作用。
import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.reflect.Field; public class Test { public static boolean isRun = true; public static void main(String[] args) throws Exception { String abc = new String("abc"); System.out.println(abc.getClass() + "@" + abc.hashCode()); final ReferenceQueue referenceQueue = new ReferenceQueue<String>(); new Thread() { public void run() { while (isRun) { Object o = referenceQueue.poll(); if (o != null) { try { Field rereferent = Reference.class .getDeclaredField("referent"); rereferent.setAccessible(true); Object result = rereferent.get(o); System.out.println("gc will collect:" + result.getClass() + "@" + result.hashCode()); } catch (Exception e) { e.printStackTrace(); } } } } }.start(); PhantomReference<String> abcWeakRef = new PhantomReference<String>(abc, referenceQueue); abc = null; Thread.currentThread().sleep(3000); System.gc(); Thread.currentThread().sleep(3000); isRun = false; } } |
結果為:
class java.lang.String@96354
gc will collect:class java.lang.String@96354