Java中的強引用,軟引用,弱引用和虛引用
從JDK1.2版本開始,把物件的引用分為四種級別,從而使程式能更加靈活的控制物件的生命週期。這四種級別由高到低依次為:強引用、軟引用、弱引用和虛引用。
一、強引用
如果一個物件具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空 間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。
例如:
- Object o=new Object();
- Object o1=o;
- o=
- o1=null;
如果顯式地設定o和o1為null,或超出範圍,則gc認為該物件不存在引用,這時就可以收集它了。可以收集並不等於就一會被收集,什麼時候收集這要取決於gc的演算法,這要就帶來很多不確定性。例如你就想指定一個物件,希望下次gc執行時把它收集了,那就沒辦法了,有了其他的三種引用就可以做到了。其他三種引用在不妨礙gc收集的情況下,可以做簡單的互動。
heap中物件有強可及物件、軟可及物件、弱可及物件、虛可及物件和不可到達物件。應用的強弱順序是強、軟、弱、和虛。對於物件是屬於哪種可及的物件,由他的最強的引用決定。如下:
- String abc=new
- 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)
如果一個物件只具有軟引用,那就類似於可有可物的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。
軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。
軟引用是主要用於記憶體敏感的快取記憶體。在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();
- Refenrence sr = new SoftReference(obj);
- //引用時
- if(sr!=null){
- obj = sr.get();
- }else{
- obj = new A();
- sr = new SoftReference(obj);
- }
三、弱引用(WeakReference)
如果一個物件只具有弱引用,那就類似於可有可物的生活用品。弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它 所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。
弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。
當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) {
- System.out.println("obj 已經被清除了 ");
- } else {
- System.out.println("obj 尚未被清除,其資訊是 "+obj.toString());
- }
- ...
- }
這類的技巧,在設計 Optimizer 或 Debugger 這類的程式時常會用到,因為這類程式需要取得某物件的資訊,但是不可以 影響此物件的垃圾收集。
四、虛引用(PhantomReference)
"虛引用"顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要用來跟蹤物件被垃圾回收的活動。
虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列(ReferenceQueue)聯合使用。當垃 圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。程式可以通過判斷引用佇列中是 否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。程式如果發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。
建立虛引用之後通過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;
- publicclass Test {
- publicstaticboolean isRun = true;
- publicstaticvoid 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() {
- publicvoid 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;
- }
- }