Java四大引用詳解:強引用、軟引用、弱引用、虛引用

mikechen的網際網路架構發表於2022-08-13

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

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,都會被回收。

舉例說明:

package com.mikechen.java.refenence;
import java.lang.ref.WeakReference;
/**
* 弱引用
*
* @author mikechen
*/
public class WeakReferenceDemo {
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference<Object> w1 = new WeakReference<Object>(o1);
        System.out.println(o1);
        System.out.println(w1.get());
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(w1.get());
    }
}

 

弱引用的應用

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的網際網路架構更多技術文章合集

| | | | | | | 架構師


關注「mikechen 的網際網路架構」公眾號,回覆 【架構】領取我原創的《300 期 + BAT 架構技術系列與 1000 + 大廠面試題答案》


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011997/viewspace-2910321/,如需轉載,請註明出處,否則將追究法律責任。

相關文章