【JVM】Java 關於強引用,軟引用,弱引用和虛引用的區別與用法

目錄

一、引用的概念

二、引用到底有什麼作用

三、弱引用的 GC 實戰

四、再理解 ThreadLocalMap 的弱引用

五、ReferenceQueue 引用隊列

六、應用場景

總結


內容來自:https://blog.csdn.net/junjunba2689/article/details/80601729

                   https://blog.csdn.net/xiaohulunb/article/details/103837418

一、引用的概念

JDK 1.2 版之後引入了軟(SoftReference)、弱(WeakReference)、虛(PhantomReference)三種引用。

強引用:最傳統的「引用」的定義,是指在程序代碼之中普遍存在的引用賦值,即類似Object obj=new Object()這種引用關係。只要強引用關係還存在,垃圾收集器就永遠不會回收掉被引用的對象。

軟引用:描述一些還有用,但非必須的對象。只被軟引用關聯着的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收範圍之中進行第二次回收,如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。

弱引用:描述那些非必須對象,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生爲止。當垃圾收集器開始工作,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

虛引用:是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的只是爲了能在這個對象被收集器回收時收到一個系統通知。

二、引用到底有什麼作用

假設我們有一個對象 Data ,還有一個對象 Entry 中依賴 Data 對象。僞代碼如下:

class Data {
    byte[] v;
}

class Entry {
    Data d;
}

Data data = new Data(new byte[10 * 1024]);

Entry entry = new Entry(data);

如果在運行過程中,data = null 後,data 對象可以被垃圾回收掉嗎?

答案是:需要看 entry 對象是否爲 null

如果 entry 一直不爲 null 的話,data 永遠不能被回收,因爲 Entry.d 變量引用了 data。

這時就可能發生內存泄漏。

那麼如何解決呢,答案就是使用軟、弱引用。

假如我們把 Entry 對 data 的依賴聲明爲一個軟引用。如果 data = null 後,垃圾回收時就可以回收 data 對象了。

    class Entry extends WeakReference<Data> {

        public Entry(Data d) {
            super(d);
        }
    }

我們可以大白話的理解爲:

如果是弱引用,我對你的依賴很柔軟薄弱,你覺得自己沒有用了,我不會強行留住你,會放你走(垃圾回收)

如果是強引用,就算你覺得自己沒有用了,我依然不讓你走(不讓垃圾回收)

比喻的總結四個引用

  • 強引用:關係非常好,你自己沒有用了,我也不會讓你走。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足問題
  • 軟引用:關係還行,你自己沒有用了,我會挽留到在系統將要發生內存溢出異常前在走。如果內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
  • 弱引用:關係就那樣,你自己沒有用了,垃圾收集員一來你就可以走。弱引用與軟引用的區別在於:只具有弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它 所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由於垃圾回收器是一個優先級很低的線程, 因此不一定會很快發現那些只具有弱引用的對象。
  • 虛引用:關係近乎敵人,我永遠得不到你,垃圾收集員一來你就可以走。主要與 ReferenceQueue 配合使用,在回收時進行一些邏輯操作(認爲是回收前執行一個回調函數)。

虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。虛引用主要用來跟蹤對象被垃圾回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要被垃圾回收。程序如果發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動。

我們可以看到,最主要還是「你自己沒有用了」這個操作,可以認爲是一個 obj = null 的操作。如果你走了,那麼我也拿不到你的信息了。

三、弱引用的 GC 實戰

@Slf4j
public class WeakReferenceExample {

    public static void main(String[] args) throws InterruptedException {
        // 10M 的緩存數據
        byte[] cacheData = new byte[10 * 1024 * 1024];

        // 將緩存數據用軟引用持有
        final WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);

        log.info("第一次 GC 前 {}", cacheData == null);
        log.info("第一次 GC 前 {}", cacheRef.get() == null);

        // 進行一次 GC 後查看對象的回收情況
        System.gc();
        Thread.sleep(1000); // 等待 GC
        log.info("第一次 GC 後 {}", cacheData == null);
        log.info("第一次 GC 後 {}", cacheRef.get() == null);

        // 將緩存數據的強引用去除,你沒有用了
        cacheData = null;
        System.gc();
        Thread.sleep(1000); //等待 GC
        log.info("第二次 GC 後 {}", cacheData == null);
        log.info("第二次 GC 後 {}", cacheRef.get() == null);
    }
    /* 打印內容如下:
    
     第一次 GC 前 false
     第一次 GC 前 false
    
    [GC (System.gc())  14908K->11560K(125952K), 0.0318128 secs]
    [Full GC (System.gc())  11560K->11425K(125952K), 0.0216147 secs]
    
     第一次 GC 後 false
     第一次 GC 後 false
    
    [GC (System.gc())  12090K->11457K(125952K), 0.0016023 secs]
    [Full GC (System.gc())  11457K->818K(125952K), 0.0093186 secs]
    
     第二次 GC 後 true
     第二次 GC 後 true
     */
}

四、再理解 ThreadLocalMap 的弱引用

   static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

如果我們的代碼中不需要 ThreadLocal 這個對象的話,即 ThreadLocal = null。但是 ThreadLocalMap 是線程的變量,如果線程一直運行,那麼 ThreadLocalMap 永遠不會爲 null。

  • 如果使用強引用,Entry 中的 k 強引用了 ThreadLocal ,ThreadLocal 永遠不能釋放。
  • 如果使用弱引用,ThreadLocal 在垃圾回收時將釋放,Entry 中的 k 將變爲 null,這樣線程中通過ThreadLocal 存入的數據就無法remove,若線程一直不執行,存儲的數據就得不到釋放,從而造成內存泄漏。

五、ReferenceQueue 引用隊列

引用隊列,用於存放待回收的引用對象。

對於軟引用、弱引用和虛引用,如果我們希望當一個對象被垃圾回收器回收時能得到通知,進行額外的處理,這時候就需要使用到引用隊列了。

在一個對象被垃圾回收器掃描到將要進行回收時 reference 對象會被放入其註冊的引用隊列中。我們可從引用隊列中獲取到相應的對象信息,同時進行額外的處理。比如反向操作,數據清理,資源釋放等。


下面使用「虛引用」與「引用隊列」實戰說明 源碼 :

  • 創建一個 Map ,Key 是一個虛引用,虛引用關聯 ReferenceQueue 隊列,每當 Key 被回收時,這個 Key 會入隊列。
  • 起一個線程不停的取隊列中的回收對象進行打印操作。
  • 向 Map 循環 N 次,每次 put 一個大小爲 1M 的字節數組,隨着內存增長,垃圾回收器開始工作。

垃圾回收器工作時,可以看到隊列中將被回收的對象信息。

@Slf4j
public class PhantomReferenceExample {

    private static final ReferenceQueue<byte[]> RQ = new ReferenceQueue<>();

    public static void main(String[] args) {
        final Map<PhantomReference<byte[]>, Object> map = new HashMap<>();

        final Thread thread = new Thread(() -> {
            try {
                int cnt = 0;
                PhantomReference<byte[]> k;
                while ((k = (PhantomReference<byte[]>) RQ.remove()) != null) {
                    log.info("第 {} 個回收對象,對象打印爲:{}", cnt++, k);
                }
            } catch (InterruptedException ignored) {
            }
        });
        thread.setDaemon(true);
        thread.start();

        for (int i = 0; i < 1000; i++) {
            map.put(new PhantomReference<>(new byte[1024 * 1024], RQ), new Object());
        }

        log.info("map.size :{}", map.size());
    }
    /* 部分輸出如下:
     * 第 789 個回收對象,對象打印爲:java.lang.ref.PhantomReference@26653222
     * 第 790 個回收對象,對象打印爲:java.lang.ref.PhantomReference@553f17c
     * 第 791 個回收對象,對象打印爲:java.lang.ref.PhantomReference@56ac3a89
     * 第 792 個回收對象,對象打印爲:java.lang.ref.PhantomReference@6fd02e5
     * 第 793 個回收對象,對象打印爲:java.lang.ref.PhantomReference@2b98378d
     * 第 794 個回收對象,對象打印爲:java.lang.ref.PhantomReference@26be92ad
     * 第 795 個回收對象,對象打印爲:java.lang.ref.PhantomReference@6d00a15d
     * map.size :1000
     */
}

一般情況我們很少使用軟、弱、虛三種引用,如果使用請深入研究其利害,避免引起不必要的 Bug ,通常情況多用於緩存操作,防止緩存無限增長導致內存溢出。

六、應用場景

  • WeakHashMap 實現類,如果 WeakHashMap 中的 Key 對象如果不需要了,WeakHashMap 內部可以配合 ReferenceQueue 引用隊列進行移除。
  • 緩存的實現,因爲緩存一般情況會長時間存活,如果緩存的元素已經失效了,使用軟弱引用配合 ReferenceQueue 引用隊列可以執行清除操作。
  • 使用虛引用,完成垃圾回收時的消息回調等操作。

總結

引用可區分爲強、軟、弱、虛四種,後三種可配合「引用隊列」進行一些回收前的操作。

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