Java四大參照詳解:強參照、軟參照、弱參照、虛參照

2022-08-13 12:04:10

面試官考察Java參照會問到強參照、弱參照、軟參照、虛參照,具體有什麼區別?本篇單獨來詳解 @mikechen

Java參照

從JDK 1.2版本開始,物件的參照被劃分為4種級別,從而使程式能更加靈活地控制物件的生命週期,這4種級別由高到低依次為:強參照、軟參照、弱參照和虛參照。

強參照

強參照是最普遍的參照,一般把一個物件賦給一個參照變數,這個參照變數就是強參照。

比如:

//  強參照
MikeChen mikechen=new MikeChen();

 

在一個方法的內部有一個強參照,這個參照儲存在Java棧中,而真正的參照內容(MikeChen)儲存在Java堆中。

如果一個物件具有強參照,垃圾回收器不會回收該物件,當記憶體空間不足時,JVM 寧願丟擲 OutOfMemoryError異常。

如果強參照物件不使用時,需要弱化從而使GC能夠回收,如下:

//幫助垃圾收集器回收此物件
mikechen=null;

 

顯式地設定mikechen物件為null,或讓其超出物件的生命週期範圍,則GC認為該物件不存在參照,這時就可以回收這個物件,具體什麼時候收集這要取決於GC演演算法。

舉例:

package com.mikechen.java.refenence;

/**
* 強參照舉例
*
* @author mikechen
*/
public class StrongRefenenceDemo {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = o1;
        o1 = null;
        System.gc();
        System.out.println(o1);  //null
        System.out.println(o2);  //java.lang.Object@2503dbd3
    }
}

 

StrongRefenenceDemo 中儘管 o1已經被回收,但是 o2 強參照 o1,一直存在,所以不會被GC回收。

 

軟參照

軟參照是一種相對強參照弱化了一些的參照,需要用java.lang.ref.SoftReference 類來實現。

比如:

String str=new String("abc");                                     // 強參照
SoftReference<String> softRef=new SoftReference<String>(str);     // 軟參照

 

如果一個物件只具有軟參照,則記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。

先通過一個例子來了解一下軟參照:

/**
* 弱參照舉例
*
* @author mikechen
*/
Object obj = new Object();
SoftReference softRef = new SoftReference<Object>(obj);//刪除強參照
obj = null;//呼叫gc

// 物件依然存在
System.gc();System.out.println("gc之後的值:" + softRef.get());

 

軟參照可以和一個參照佇列(ReferenceQueue)聯合使用,如果軟參照所參照物件被垃圾回收,Java虛擬機器器就會把這個軟參照加入到與之關聯的參照佇列中。

ReferenceQueue<Object> queue = new ReferenceQueue<>();
Object obj = new Object();
SoftReference softRef = new SoftReference<Object>(obj,queue);//刪除強參照
obj = null;//呼叫gc
System.gc();
System.out.println("gc之後的值: " + softRef.get()); // 物件依然存在
//申請較大記憶體使記憶體空間使用率達到閾值,強迫gc
byte[] bytes = new byte[100 * 1024 * 1024];//如果obj被回收,則軟參照會進入參照佇列
Reference<?> reference = queue.remove();if (reference != null){
    System.out.println("物件已被回收: "+ reference.get());  // 物件為null
}

 

軟參照通常用在對記憶體敏感的程式中,比如快取記憶體就有用到軟參照,記憶體夠用的時候就保留,不夠用就回收。

我們看下 Mybatis 快取類 SoftCache 用到的軟參照:

public Object getObject(Object key) {
    Object result = null;
    SoftReference<Object> softReference = (SoftReference)this.delegate.getObject(key);
    if (softReference != null) {
        result = softReference.get();
        if (result == null) {
            this.delegate.removeObject(key);
        } else {
            synchronized(this.hardLinksToAvoidGarbageCollection) {
                this.hardLinksToAvoidGarbageCollection.addFirst(result);
                if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) {
                    this.hardLinksToAvoidGarbageCollection.removeLast();
                }
            }
        }
    }
    return result;}

 

注意:軟參照物件是在jvm記憶體不夠的時候才會被回收,我們呼叫System.gc()方法只是起通知作用,JVM什麼時候掃描回收物件是JVM自己的狀態決定的,就算掃描到軟參照物件也不一定會回收它,只有記憶體不夠的時候才會回收。

 

弱參照

弱參照的使用和軟參照類似,只是關鍵字變成了 WeakReference:

MikeChen mikechen = new MikeChen();
WeakReference<MikeChen> wr = new WeakReference<MikeChen>(mikechen );

 

弱參照的特點是不管記憶體是否足夠,只要發生 GC,都會被回收。

舉例說明:

public class WeakHashMapDemo {

    public static void main(String[] args) throws InterruptedException {
        myHashMap();
        myWeakHashMap();
    }

    public static void myHashMap() {
        HashMap<String, String> map = new HashMap<String, String>();
        String key = new String("k1");
        String value = "v1";
        map.put(key, value);
        System.out.println(map);

        key = null;
        System.gc();

        System.out.println(map);
    }

    public static void myWeakHashMap() throws InterruptedException {
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
        //String key = "weak";
        // 剛開始寫成了上邊的程式碼
        //思考一下,寫成上邊那樣會怎麼樣? 那可不是參照了
        String key = new String("weak");
        String value = "map";
        map.put(key, value);
        System.out.println(map);
        //去掉強參照
        key = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println(map);
    }}

 

弱參照的應用

WeakHashMap

public class WeakHashMapDemo {

    public static void main(String[] args) throws InterruptedException {
        myHashMap();
        myWeakHashMap();
    }

    public static void myHashMap() {
        HashMap<String, String> map = new HashMap<String, String>();
        String key = new String("k1");
        String value = "v1";
        map.put(key, value);
        System.out.println(map);

        key = null;
        System.gc();

        System.out.println(map);
    }

    public static void myWeakHashMap() throws InterruptedException {
        WeakHashMap<String, String> map = new WeakHashMap<String, String>();
        //String key = "weak";
        // 剛開始寫成了上邊的程式碼
        //思考一下,寫成上邊那樣會怎麼樣? 那可不是參照了
        String key = new String("weak");
        String value = "map";
        map.put(key, value);
        System.out.println(map);
        //去掉強參照
        key = null;
        System.gc();
        Thread.sleep(1000);
        System.out.println(map);
    }}

 

當key只有弱參照時,GC發現後會自動清理鍵和值,作為簡單的快取表解決方案。

ThreadLocal

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    //......}

 

ThreadLocal.ThreadLocalMap.Entry 繼承了弱參照,key為當前執行緒範例,和WeakHashMap基本相同。

 

虛參照

虛參照」顧名思義,就是形同虛設,與其他幾種參照都不同,虛參照並不會決定物件的生命週期。如果一個物件僅持有虛參照,那麼它就和沒有任何參照一樣,在任何時候都可能被垃圾回收器回收。

虛參照也稱為「幽靈參照」或者「幻影參照」,它是最弱的一種參照關係。

虛參照需要java.lang.ref.PhantomReference 來實現:

A a = new A();
ReferenceQueue<A> rq = new ReferenceQueue<A>();
PhantomReference<A> prA = new PhantomReference<A>(a, rq);

 

虛參照主要用來跟蹤物件被垃圾回收器回收的活動。

虛參照與軟參照和弱參照的一個區別在於:虛參照必須和參照佇列 (ReferenceQueue)聯合使用,當垃圾回收器準備回收一個物件時,如果發現它還有虛參照,就會在回收物件的記憶體之前,把這個虛參照加入到與之 關聯的參照佇列中。

 

Java參照總結

java4種參照的級別由高到低依次為:強參照 > 軟參照 > 弱參照 > 虛參照。

以上

作者簡介

陳睿|mikechen,10年+大廠架構經驗,《BAT架構技術500期》系列文章作者,分享十餘年BAT架構經驗以及面試心得!

閱讀mikechen的網際網路架構更多技術文章合集

Java並行|JVM|MySQL|Spring|Redis|分散式|高並行|架構師