天天看點

深入了解 Java中的軟引用,弱引用和虛引用

轉載來源:http://my.eoe.cn/ymcao/archive/3623.html

Java中的軟引用,弱引用和虛引用

 在Android的圖檔進行中,碰到的一個非常普遍的問題便是OOM錯誤 為此網上也有很多例子,而在之前的一篇轉載裡 提到了ListView中加載圖檔的ImageLoader,而其中有一處,使用到了名為SoftPreference的類 這是Java中的一個類 也就是所謂的軟引用 在查詢了相關的資料以後 會發現SoftPreference的特性,非常适合用來處理OOM引起的問題:

  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 abcSoftRef=new SoftReference(abc); //2

WeakReference abcWeakRef = new WeakReference(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 的技巧。

1      
A obj = new A();
      

SoftRefenrence sr = new SoftReference(obj);

1
2
3
4
5
6
7      
//引用時
if(sr!=null){
    obj = sr.get();
}else{
    obj = new A();
    sr = new SoftReference(obj);
}
      

 弱引用

  當gc碰到弱可及對象,并釋放abcWeakRef的引用,收集該對象。但是gc可能需要對此運用才能找到該弱可及對象。通過如下代碼可以了明了的看出它的作用:

String abc=new String("abc");

WeakReference abcWeakRef = new WeakReference(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();

1
2
3
4
5      
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")對象之前,你就可以做一些其他的事情。通過以下代碼可以了解他的作用。

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;

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33      
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.[email protected]

  gc will collect:class [email protected]