【Java基礎】四種引用(強引用、軟引用、弱引用、虛引用)

java中的引用類似C/C++中的指針,都是指向內存中的某個對象,通過該引用可以直接操作對應的對象。引用和GC有着直接關係,根據可達性分析算法(從一個被稱爲 GC Roots 的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用),一個對象沒有引用關聯,則GC可以將其回收。

1、強引用

引用的默認方式,即是強引用,創建一個對象並把這個對象賦給一個引用變量,如Object obj = new Object();

強引用有引用變量指向時永遠不會被垃圾回收,JVM寧願拋出OutOfMemory錯誤也不會回收這種對象.

如果分配了一個大對象,如String[] strings = new String[1024 * 1024 * 600](-Xms512M -Xmx512M);jvm會拋OOM。

如果想中斷強引用和某個對象之間的關聯,需要顯式地將引用賦值爲null,如HashMap的clear操作

public void clear() {
    Node<K,V>[] tab;
    modCount++;
    if ((tab = table) != null && size > 0) {
        size = 0;
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null;//此處顯式地將引用賦值爲null,斷開了強引用關係,便於GC
    }
}

2、軟引用(SoftReference)

如果一個對象具有軟引用,內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用常被用作緩存,如圖片、網頁緩存等。

用法爲:

byte[] bytes = new byte[1024 * 1024 * 100];

SoftReference<byte[]> softReference = new SoftReference<>(bytes);

此時對象有兩個引用,第1行的強引用,第3行的軟引用。由於有強引用存在,對象此時是不會被回收的。

此時將bytes = null;對象只有軟引用,當內存不足時,是可以回收該對象的。

byte[] bytes = new byte[1024 * 1024 * 100];

SoftReference<byte[]> softReference = new SoftReference<>(bytes);
System.out.println(softReference.get());//[B@d716361

bytes = null;

System.gc();

System.out.println(softReference.get());//[B@d716361

byte[] bytes2 = new byte[1024 * 1024 * 300];

System.out.println(softReference.get());//null

可以看到,第10行get時還是可以取到值的,因爲此時內存充裕。當第12行需要分配一個大對象時,那對不起,要把你這個軟引用對象回收了,因爲此時內存快不夠了。垃圾收集線程會在虛擬機拋出OOM之前回收軟引用對象,而且虛擬機會儘可能優先回收長時間閒置不用的軟引用對象,對那些剛剛構建的或剛剛使用過的“新”軟引用對象會被虛擬機儘可能保留。

軟引用使用的一個例子可以參見Android的圖片緩存

3、弱引用(WeakReference)

弱引用比軟引用優先級更低,gc時,不管內存還充不充裕,都會回收弱引用對象。

byte[] bytes = new byte[1024 * 1024 * 100];

WeakReference<byte[]> weakReference = new WeakReference<>(bytes);
bytes = null;
System.out.println(weakReference.get());//[B@d716361

System.gc();

System.out.println(weakReference.get());//null

4、虛引用(PhantomReference)

Phantom有幻覺、幽靈之意。聽名字就知道是這幾種引用中最弱的,跟不存在似的。如果對象僅僅存在虛引用,那麼GC可以隨時回收它,明白爲什麼叫Phantom了吧。

看下PhantomReference的代碼:

public class PhantomReference<T> extends Reference<T> {
    public T get() {
        return null;
    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

PhantomReference就一個方法get(直接返回null,所以調用了它也沒啥意義),一個構造函數(入參有個引用隊列,說明了PhantomReference必須和引用隊列一起使用)

5、引用隊列(ReferenceQueue)

查看軟引用、弱引用、虛引用的代碼可以看到,軟引用、弱引用可以和引用隊列一起使用,也可以不一起,而虛引用必須和引用隊列一起使用。那麼引用隊列是什麼?

引用隊列,顧名思義,引用組成的隊列,引用指的是Reference,SoftReference、WeakReference、PhantomReference均繼承此類,該類中有幾個屬性:

private volatile Reference<? extends T> head = null;
private T referent;         /* Treated specially by GC */

volatile ReferenceQueue<? super T> queue;

/* When active:   NULL
 *     pending:   this
 *    Enqueued:   next reference in queue (or this if last)
 *    Inactive:   this
 */
@SuppressWarnings("rawtypes")
volatile Reference next;

head是隊列維持的頭指針,referent即各自引用的對象,queue是創建引用時傳入的引用隊列,next指向下一個引用。

byte[] bytes = new byte[1024 * 1024 * 100];
ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
bytes = null;

在上面的代碼中,weakReference所引用的對象會在下次GC的時候被回收掉,自己也會被加入rq的隊列(每個引用被放進來的時候,會被加到前一個引用的前面,即Reference的next指向前一個加入進來的Reference,說是加入進隊列,其實是將隊列的head和各個Reference中的next串聯起來形成鏈表)。爲什麼要這樣做呢,weakReference引用的對象雖然在下次GC的時候被清除掉了,它自身也沒有存在的價值了,但是weakReference本身也是強引用類型啊,自身也是佔用內存的。所以weakReference被放到引用隊列的時候,我們就可以把它取出來清掉了。

ReferenceQueue提供了三種取出Reference的方法:

public Reference<? extends T> poll();//非阻塞,取出head鎖指向的Reference
public Reference<? extends T> remove();//阻塞,直到head不爲null,即隊列中有Reference
public Reference<? extends T> remove(long timeout);//阻塞,和上一個一樣,不過加了個超時時間

具體使用可以參見WeakHashMap分析

6、總結

  • 四種引用的可及性優先級:強引用>軟引用>弱引用>虛引用。當一個對象同時有多種引用類型時,可及性按高優先級處理。如
Object object = new Object();
WeakReference<Object> weakReference = new WeakReference<>(object);

同時有強引用和弱引用存在,那麼這個對象就是強可及的。GC的時候不會回收。

  • 軟引用和弱引用最好和引用隊列一起使用,這樣你能知道引用對象何時被回收及做出一些相應處理。虛引用必須和引用隊列一起使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章