【Java基礎】理解強引用、軟引用、弱引用、虛引用、引用隊列

一.Java中的四種引用

Java中有四種引用類型依次爲:

  • 強引用(Strong Reference
  • 軟引用(Soft Reference
  • 弱引用(Weak Reference
  • 虛引用(Phantom Reference

這四種引用強度依次逐漸減弱

二.Java爲什麼要設計這四種引用

Java 中引入四種引用的目的是讓程序自己決定對象的生命週期,JVM 是通過垃圾回收器GC對這四種引用做不同的處理,來實現對象生命週期的改變。

JDK 8中的 UML關係圖
在這裏插入圖片描述
FinalReference 類是包內可見,其他三種引用類型均爲 public,可以在應用程序中直接使用。

Java設計這四種引用的主要目的有兩個:

  • 可以讓程序員通過代碼的方式來決定某個對象的生命週期;
  • 有利用垃圾回收

三.瞭解四種引用類型

1.強引用

  • 強引用是最普遍的一種引用,我們寫的代碼,99.9999%都是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。類似 Object obj = new Object()這類的引用。只要某個對象有強引用與之關聯,這個對象永遠不會被回收,即使內存不足,JVM寧願拋出OOM,也不會去回收。

  • 當一個對象被強引用變量引用時,它處於可達狀態,是不可能被垃圾回收器回收的,即使該對象永遠不會被用到也不會被回收

    Java垃圾回收器GC採用可達性分析法 來確認對象是否存活。如果一個對象沒有任何對象所引用,這個對象屬於不可達對象,在下一次GC會被回收。反之,當一個對象處於可達對象,該對象不會被GC所回收

  • 當內存空間不足,Java虛擬機寧願拋出OOM錯誤,使程序異常終止,也不會回收這種對象。因此強引用有時也是造成Java 內存泄露的原因之一。

  • 對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯示地將相應(強)引用賦值爲 null,一般認爲就是可以被垃圾收集器回收。(具體回收時機還要要看垃圾收集策略)。

例如:

StringBuffer str = new StringBuffer("hello world");

局部變量str會被放到裏,而StringBuffer實例對象會被放在堆內,局部變量str指向堆內的StringBuffer對象,通過str可以操作該對象,那麼str就是StringBuffer的強引用
在這裏插入圖片描述
當執行這條語句

StringBuffer str1 = str;


在這裏插入圖片描述
此時這兩個引用都是強引用

強引用具備如下特點:

  1. 通過強引用可以直接訪問目標對象
  2. 強引用所指向的對象在任何時候都不會被系統回收,虛擬機寧願拋出OOM(內存溢出)異常,也不會回收強引用所指向的對象
  3. 強引用可能導致內存泄漏(站着空間不釋放,積累的多了內存泄漏會導致內存溢出)

代碼演示

    @Test
    public void testStrongReference() {
        Object o1 = new Object();
        Object o2 = o1;
        
        //將強引用去除
        o1 = null;
        
        System.gc();
        System.out.println(o1);  //null
        System.out.println(o2);  //java.lang.Object@2503dbd3
    }

執行結果
在這裏插入圖片描述
o1已經被回收,但是 o2 強引用 o1,一直存在,所以不會被GC回收

那麼什麼時候纔可以被回收呢
如果想中斷強引用和某個對象之間的關聯,可以顯示地將引用賦值爲null,這樣一來的話,JVM在合適的時間就會回收該對象。

o1 = null;
o2 = null;

2.軟引用

  • 軟引用是相對強引用弱化了一些的引用,Java1.2後用java.lang.ref.SoftReference類來實現,可以讓對象避免一些垃圾收集。

    軟引用就是把對象用SoftReference包裹一下,當我們需要從軟引用對象獲得包裹的對象,只要get一下就可以了:

  • 主要用來描述一些還有用,但並非必需的對象被軟引用變量引用的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中並進行第二次回收。如果這次回收還是沒有足夠的內存,纔會拋出內存溢出異常。

  • 對於只有軟引用的對象來說:當系統內存充足時它不會被回收,當系統內存不足時它纔會被回收。

代碼演示
在這裏插入圖片描述

import java.lang.ref.SoftReference;
//VM options: -Xms5m -Xmx10m
public class SoftRefenenceDemo {

    public static void main(String[] args) {
        softRefMemoryEnough();//內存夠用時軟引用的處理

        System.out.println("------內存不夠用的情況------");

        softRefMemoryNotEnough();//內存不足時軟引用的處理
    }

    /**
     * 內存夠用時軟引用的處理
     */
    private static void softRefMemoryEnough() {
        Object o1 = new Object();
        //使用軟引用變量引用 o1
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());
        
        //將強引用去除
        o1 = null;
        
        System.gc();

        System.out.println(o1);
        System.out.println(s1.get());
    }

     /**
      * 內存不足時軟引用的處理
      *
     * JVM配置`-Xms5m -Xmx10m` ,然後故意new一個一個大對象,使內存不足產生 OOM,看軟引用回收情況
     */
    private static void softRefMemoryNotEnough() {
        Object o1 = new Object();
        //使用軟引用變量引用 o1
        SoftReference<Object> s1 = new SoftReference<Object>(o1);
        System.out.println(o1);
        System.out.println(s1.get());
        
        //將強引用去除
        o1 = null;

        try {
            //創建10M對象
            byte[] bytes = new byte[10 * 1024 * 1024];
        }catch (OutOfMemoryError e) {
           e.printStackTrace();
        }

        System.out.println(o1);
        System.out.println(s1.get());
    }
}

執行結果:
在這裏插入圖片描述

可以很清楚的看到當內存不足時,會觸發JVM的GC,如果GC後,內存還是不足,就會把軟引用的包裹的對象給幹掉,如果幹掉軟引用的包裹的對象後內存還是不足就會拋出OOM, 也就是隻有在內存不足,JVM纔會回收該對象

此外,軟引用還可以附帶一個引用隊列ReferenceQueue,當對象可達性發生改時(由可達變爲不可達,被回收),此時軟引用進入隊列,通過這個引用隊列,可以跟蹤對象的回收情況

軟引用到底有什麼用呢?

  • 根據軟引用特性,非常適合保存一些可有可無的緩存,如果這麼做,當系統內存空間不足時,會及時回收他們,不會導致內存溢出,當系統內存資源充足時,這些還存有可以存在相當長的時間,提升系統速度。

3.弱引用

  • 弱引用也是用來描述非必需對象的,它的強度比軟引用更弱一些被弱引用變量引用的對象只能生存到下一次垃圾收集發生之前。 當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

  • Java1.2後用java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短。

    弱引用的使用和軟引用類似,使用WeakReference包裹對象即可:

  • 對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管 JVM 的內存空間是否足夠,都會回收該對象佔用的內存。

import java.lang.ref.WeakReference;

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());
    }
}

執行結果:
在這裏插入圖片描述

可以很清楚的看到明明內存還很充足,但是觸發了GC,資源還是被回收了。弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap

使用WeakHashMap

public class WeakHashMapDemo {
    public static void main(String[] args) throws InterruptedException {
        myHashMap();
        System.out.println("-------------------");
        myWeakHashMap();
    }

    /**
     * 測試GC HashMap
     */
    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("GC——>HashMap");

        System.out.println(map);
    }

    /**
     * 測試GC 弱引用WeakHashMap
     * @throws InterruptedException
     */
    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();
        System.out.println("GC——>WeakHashMap");

        Thread.sleep(1000);
        System.out.println(map);
    }
}

執行結果:
在這裏插入圖片描述
在這裏插入圖片描述

4.引用隊列

ReferenceQueue 是用來配合引用工作的,沒有ReferenceQueue 一樣可以運行。

ReferenceQueue是專門用來存放引用的, 當軟引用,弱引用,虛引用對應的那個對象被回收後的同時,該引用會自動加入到你所定義的ReferenceQueue中.

  • SoftReferenceWeakReferencePhantomReference 都有一個可以傳遞 ReferenceQueue 的構造器。

  • 創建引用的時候,可以指定關聯的隊列當 GC 釋放對象內存的時候,會將引用加入到引用隊列。 如果程序發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的內存被回收之前採取必要的行動,這相當於是一種通知機制。

  • 當關聯的引用隊列中有數據的時候,意味着指向的堆內存中的對象被回收。通過這種方式,JVM 允許我們在對象被銷燬後,做一些我們自己想做的事情。

在這裏插入圖片描述

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

public class TestReferenceQueue {
    public static void main(String[] args) {
        ReferenceQueue rq = new ReferenceQueue();
        WeakReference wr = new WeakReference(new TestReferenceQueue(), rq);
        System.out.println("弱引用對應的對象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("隊列中對象:" + rq.poll());
        /**
         * TestReferenceQueue中的對象只有一個引用 就是wr弱引用
         * 因此直接調用gc就可以
         */
        System.gc();
        System.out.println("弱引用對應的對象:" + wr.get() + ", 弱引用本身:" + wr);
        System.out.println("隊列中對象:" + rq.poll());
    }
}

//弱引用對應的對象:com.demo.reference.TestReferenceQueue@3e57cd70, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//隊列中對象:null
//弱引用對應的對象:null, 弱引用本身:java.lang.ref.WeakReference@9a7504c
//隊列中對象:java.lang.ref.WeakReference@9a7504c

執行結果
在這裏插入圖片描述
在弱引用對應的對象被回收後該弱引用對象本身也進入到了ReferenceQueue中,ReferenceQueue清除失去了弱引用對象的弱引用本身, 軟引用,虛引用也是如此.

5.虛引用(對象回收和跟蹤)

虛引用也稱爲 “幽靈引用” 或者 “幻影引用” ,它是最弱的一種引用關係。

  • 虛引用,顧名思義,就是形同虛設,與其他幾種引用都不太一樣,一個對象被虛引用變量所引用的,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。

  • Java1.2後用java.lang.ref.PhantomReference 來實現。

  • 如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪問對象,虛引用必須和引用隊列(RefenenceQueue)聯合使用。

虛引用的特點之一:虛引用必須與ReferenceQueue一起使用,當GC準備回收一個對象,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。

  • 虛引用的主要作用是跟蹤對象垃圾回收的狀態。僅僅是提供了一種確保對象被finalize 以後,做某些事情的機制。

  • PhantomReferenceget方法總是返回 null,因此無法訪問對應的引用對象。其意義在於說明一個對象已經進入 finalization 階段,可以被 GC 回收,用來實現比 finalization 機制更靈活的回收操作。

    虛引用特點之兒:無法通過虛引用來獲取對一個對象的真實引用

    • 換句話說,設置虛引用的唯一目的,就是在這個對象被回收器回收的時候收到一個系統通知或者後續添加進一步的處理。
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;

public class PhantomReferenceDemo {

    public static void main(String[] args) throws InterruptedException {
        //聲明強用對象
        Object o1 = new Object();
        //聲明引用對象
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<Object>();
        //虛引用中配置引用對象和引用隊列
        PhantomReference<Object> phantomReference = new PhantomReference<Object>(o1, referenceQueue);

        System.out.println(o1);//null
        System.out.println(referenceQueue.poll());//null
        System.out.println(phantomReference.get());//null

        o1 = null;  
        System.gc();
        
        Thread.sleep(3000);

        System.out.println(o1);//null
        System.out.println(referenceQueue.poll()); //引用隊列中  java.lang.ref.PhantomReference@6e2c634b
        System.out.println(phantomReference.get()); // null
    }
}

執行結果
在這裏插入圖片描述
當發生GC,虛引用就會被回收,並且會把回收的通知放到ReferenceQueue中。

虛引用有什麼用呢?

  • 由於虛引用可以跟蹤對象的回收時間,所以可以將一些資源的釋放操作放置在虛引用中執行和記錄

Java 允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。(但不推薦在代碼中使用)

6.總結

引用類型 被回收時間 用途 生存時間
強引用 從來不會 對象的一般狀態 JVM停止運行時
軟引用 內存不足時 對象緩存 內存不足時
弱引用 jvm垃圾回收時 對象緩存 gc運行後
虛引用 未知 未知 未知

在實際程序設計中一般很少使用弱引用與虛引用使用軟引用的情況較多,這是因爲軟引用可以加速JVM對垃圾內存的回收速度,可以維護系統的運行安全,防止內存溢出(OutOfMemory)等問題的產生

  • 利用軟引用和弱引用解決OOM問題:假如有一個應用需要讀取大量的本地圖片,如果每次讀取圖片都從硬盤讀取,則會嚴重影響性能,但是如果全部加載到內存當中,又有可能造成內存溢出,此時使用軟引用可以解決這個問題。

  • 設計思路是:用一個HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了OOM的問題。

Reference源碼(JDK8)

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