Android的四種引用(強引用、弱引用、軟引用、虛引用)

前言:滿紙荒唐言,一把辛酸淚;都雲作者癡,誰解其中味。

一、概述

早在JDK1.2就把對象引用分爲四種級別,從而使程序能更靈活控制它的生命週期,級別由高到底依次爲:強 > 軟 > 弱 > 虛引用。而GC垃圾回收器(Garbage Collection)對不同的類型有着不同的處理方法,瞭解這些處理方式有助於我們寫出更高質量的代碼。

在Java中,一切被視爲對象,引用則是用來操縱對象的。對象和引用之間的關係可以用遙控器(引用)操控電視機(對象)這個場景來理解。遙控器只要鏈接電視機那麼就能操控電視機,當我們嘗試操控一個未指向任何對象的引用去操作對象時,就會出現空指針異常(NullPointerException),就好比遙控器沒有鏈接任何電視機,你卻去操控遙控器,那是沒法操控的。

Java有一個重要的有點就是通過垃圾回收機制自動管理內存回收,開發者不需要調用函數來釋放內存,內存的分配是由程序分配的,而內存的回收由內存來完成。GC爲了釋放對象,會監控每一個對象的運行狀態,包括申請,引用,被引用和賦值,GC都需要監控。監控對象的狀態是爲了更加準確地,及時地釋放對象,釋放對象的原則是該對象不再被引用。但是由於我們因爲各種原因導致一些對象使用完後扔被持有引用而沒有被回收導致內存泄漏。

總結了幾個引用,下面我們在一一細講:

引用類型 說明 使用場景
強引用(StrongReference) 不會自動回收,最難被GC回收的,寧可拋出異常也不回收強引用指向的對象 任何場景
軟引用(SoftReference) 內存不足時,GC會回收軟引用指向的對象 比較少使用,已被LruCache替代
弱引用(WeakReference) 不管內存足不足,只要GC了都能回收弱引用指向的對象 常用於避免內存泄漏
虛引用(PhantomReference) 隨時都能回收,也稱幽靈引用,相當於沒有指向任何實例引用 跟蹤對象是否被回收,很少使用

二、引用詳解

1.強引用

強引用(StrongReference)是使用最普遍的引用,如果一個對象屬於強引用,不會自動被回收,當內存不足時,jvm寧願拋出OOM使程序異常也不會回收強引用。當我們使用一個new關鍵字去創建對象的時候,這個對象的引用就是強引用。

//str表示強引用,指向new String()這個對象
String str = new String();

2.軟引用

軟引用(SoftReference)是除了強引用外,最強的引用類型。如果系統內存足夠是不會回收軟引用指向的對象的,如果系統內存不足那麼就會回收。使用軟引用,在OutOfMemory異常發生前,軟引用指向的對象就可以被釋放掉,避免內存達到上限避免crash的發生。

   //創建軟引用實例,將bitmap存入到軟引用中
   SoftReference<Bitmap> softReference = new SoftReference<>(bitmap);
   Bitmap bitmap = softReference.get()

   if ("內存不足"){
        //將軟引用中的對象設置爲null,否則既持有強引用也持有軟引用是無法回收的
        bitmap = null;
        //通知系統回收
        System.gc();
    }

注意:軟引用是在內存不足的時候纔會被回收,我們調用System.gc()只是起到通知的作用,JVM什麼時候掃描回收對象是它自己的態度決定,就算掃描到軟引用也不一定會回收它,只有在內存不足的時候纔回收。

我們嘗試使用軟引用來保存圖片實例;

   ImageView imageView = findViewById(R.id.iv_soft_reference);
   Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round);
    //創建軟引用實例,將bitmap存入到軟引用中
   SoftReference<Bitmap> softReference = new SoftReference<>(bitmap);
        if (softReference.get() != null) {
        imageView.setImageBitmap(softReference.get());
    }

通過軟引用的get()方法獲取強Bitmap對象實例的引用,如果對象未被回收那麼設置圖片到控件上。如果內存緊張,系統就會GC,softReference.get()就不會反悔Bitmap對象,而是反回null,這裏也需要把軟引用獲取的引用做空判斷,避免空指針。

實際情況中,使用軟引用雖然可以避免OOM,但是不適用於某些場景,使用的比較少。在Android開發中,一種更好的選擇是LruCache,LRU是Least Recently Used的縮寫,即“最近最少使用”的意思,它內部會維護一個固定大小的內存,當內存不足時,會把最近最少使用的數據移除掉。

3.弱引用

弱引用(WeakReference)是弱於軟引用的引用類型,與軟引用類似,不同的是弱引用不能阻止垃圾回收,在垃圾回收機制運行時,如果一個對象的引用是弱引用的話,不管內存空間是否足夠,對象都會被回收。弱引用常常被用於防止內存泄漏,最常見的是單例和Handler造成的內存泄漏。

//弱引用實例
WeakReference<Context> weakReference = new WeakReference<>(context);
//獲取弱引用保存的引用
Context ctx = weakReference.get();

注意:如果一個對象比較少使用,並且洗碗在使用是隨時就獲取到,又不想影響次對象的垃圾回收,你可以用WeakReference來標記此對象。

我們來看看單例造成的內存泄漏:

public class InstanceClass {
    private Context mContext;
    private static InstanceClass mInstanceClass;

    public InstanceClass(Context context) {
        this.mContext = context;
    }

    //傳入的Context如果外部類(比如Activity)需要銷燬時,InstanceClass仍然持有Context,導致Activity無法銷燬回收
    public static InstanceClass getInstance(Context context) {
        if (mInstanceClass == null) {
            mInstanceClass = new InstanceClass(context);
        }
        return mInstanceClass;
    }
}

在Activity中使用該實例:

 InstanceClass instanceClass = InstanceClass.getInstance(this);

單例是開發中比較常用的設計模式,但是使用不當也會造成內存泄漏,如上面例子,InstanceClass通過構造方法持有外部類Activity的引用,因爲InstanceClass中的實例是static修飾的,所以InstanceClass的聲明週期時和應用聲明週期一致,如果當外部類銷燬的時候,InstanceClass仍然持有Context,導致Activity無法銷燬回收,就會導致內存溢出,我們用圖形來說明一下:

那麼我們可以在InstanceClass構造的時候將Activity的引用存入到弱引用中,需要使用的時候再出來;

public class InstanceClass {
    private Context mContext;
    private static InstanceClass mInstanceClass;

    public InstanceClass(Context context) {
        //構造弱引用實例,將context存入弱引用中
        WeakReference<Context> mWeakReference = new WeakReference<>(context);
        //從弱引用中獲取,mWeakReference.get()可能爲空,需要做空判斷
        this.mContext = mWeakReference.get();
    }

    //傳入的Context如果外部類(比如Activity)需要銷燬時,InstanceClass仍然持有Context,導致Activity無法銷燬回收
    public static InstanceClass getInstance(Context context) {
        if (mInstanceClass == null) {
            mInstanceClass = new InstanceClass(context);
        }
        return mInstanceClass;
    }
}

如果Activity被回收了那麼mWeakReference.get()就會返回null,這裏需要做空判斷。

4.虛引用

虛引用(PhantomReference)是最弱的引用,一個持有虛引用的對象和沒有引用幾乎是一樣的,隨時都可能被垃圾回收器回收。通過虛引用的get()方法獲取到的引用都會失敗(爲null),虛引用必須和引用隊列ReferenceQueue一起使用。

ReferenceQueue引用隊列作用在於跟蹤垃圾回收過程。當垃圾回收器回收對象時,如果發現它還有虛引用,就會在回收後銷燬這個對象,並且將虛引用指向的對象加入到引用隊列。只能通過虛引用是否被加入到ReferenceQueue來判斷虛引用是否爲GC回收,這也是判斷對象是否爲回收的唯一途徑。

Java的Object類中有finalize()方法,原理:一旦垃圾回收器準備釋放對象佔用的內存空間,將首先調用finalize()方法,並且在下一次垃圾回收動作發生時,纔會真正回收對象佔用的內存,但是問題在於,虛擬機不能保證finalize()何時被調用,因爲GC運行時間不是固定的。

使用虛引用就能解決這個問題,虛引用主要用於跟蹤垃圾被回收的活動,主要用來實現比較精細的內存使用控制,這對Android來說很有意義,比如我們可以在確定一個Bitmap被回收後,再去申請另個Bitmap的內存,通過這種方式可以使程序消耗的內存維持在一個相對較低較穩定的水平。

//引用隊列
ReferenceQueue<Object> queue = new ReferenceQueue<>();
//虛引用
PhantomReference<Object> phantomReference = new PhantomReference<Object>(new Object(), queue);

Log.e(TAG, "虛引用:PhantomReference == " + phantomReference.get());

//系統垃圾回收
System.gc();
System.runFinalization();

Log.e(TAG, "虛引用:PhantomReference == 是否被回收:" + (queue.poll() == phantomReference) + " | 隊列中的PhantomReference == " + queue.poll());

phantomReference.get()獲取的引用一直爲null,調用系統回收垃圾,queue.poll()獲取保存的引用對象,並且把它在這個隊列中移除,打印log如下:

虛引用無法通過get()方法獲取目標的引用,一直都是返回null,可以看源碼:

    public T get() {
        return null;
    }

三、回收驗證

上面我們驗證了虛引用的垃圾回收,我們這次來驗證一下強引用、軟引用和弱引用的垃圾回收。這裏我們使用System.gc(),如果在Android中使用這個API的話,系統只是通知GC,但是什麼時候GC我並不知道,所以我們要把例子放在Java的main方法中執行,因爲Java和Android的垃圾收集器是不同的,Android不是使用標準的JVM,而是使用Dalvik VM。

1.弱引用垃圾回收驗證

在main方法中執行:創建強引用對象,將引用存放到弱引用中,然後手動調用系統gc;

   public static void main(String[] args){
        String str = new String("value");
        WeakReference<String> weakReference = new WeakReference<>(str);
        str = null;
        System.out.println("弱引用:WeakReference == GC回收前:" + weakReference.get());

        //系統GC垃圾回收
        System.gc();

        System.out.println("弱引用:WeakReference == GC回收後:" + weakReference.get());
    }

這裏需要str = null; 如果不設置那麼結果返回的都是value,爲什麼呢?因爲如果你不將str = null,那麼對象str 既持有強引用,也持有弱引用,當然不會被回收;將打印數據如下:

在Andorid中(Activity)中執行上面的代碼,那麼打印的都是value,因爲System.gc()只是通知系統去垃圾回收,沒有立即去回收,並不知道它什麼時候回收。

至此,本文結束!

 

源碼地址:https://github.com/FollowExcellence/AndroidOptimizeDemo

請尊重原創者版權,轉載請標明出處: https://blog.csdn.net/m0_37796683/article/details/10316956謝謝!

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