1. 程式人生 > >Android 軟引用和弱引用的使用分析

Android 軟引用和弱引用的使用分析

本文目錄

1. 引用型別

Java 語言的引用型別有這幾種

  • 強引用
  • 軟引用 SoftReference
  • 弱引用 WeakReference

強引用只要有被引用,虛擬機器 gc 的時候是不會清理的,我們平時寫的程式碼沒有特別指明什麼引用累心,都是強引用。所以如果程式碼有缺陷,強引用無法被 gc,就會出現記憶體洩漏,Leak 到一定的程度,就會出現 OOM(Out Of Memory Error)。

2. 記憶體洩漏

Java 的記憶體洩漏和 C++ 不一樣。Java 虛擬機器有自己的垃圾回收器,採用引用計數的方式,按照一定的策略回收 Java 堆中被引用數為 0 的物件。Java 沒有區域性的物件,所有物件的記憶體都在堆裡,所以理論上是不需要我們去關心釋放問題的。如果出現了某個物件,從業務邏輯上看已經不再使用,但是卻仍有其他物件對其持有強引用,那這個物件的堆記憶體就無法回收了。在 Android 上,洩漏的規模如果達到 DVM 的限制,就會發生 OOM。

這個記憶體的限制和具體的手機 ROM 有關,我用小米5發現可以到 500M,而用的一個很舊的古董機只能 100多M。

為什麼物件業務邏輯上沒有用了,卻還會被其他物件引用了?

每個物件在業務邏輯上都是有生命週期的。如果生命週期長的物件引用了宣告週期短的物件,那生命週期短的物件什麼時候可以釋放記憶體就要看這個生命週期長的物件什麼時候把引用釋放。

如果這個生命週期長的物件長期持有引用,生命週期短的物件就不會及時回收。如果這個生命週期長的物件持有了多個生命週期短的物件,那在記憶體上的表現,就會出現增長,如果控制不好就會 OOM。等生命週期長的物件釋放這些引用了,比如這個生命週期長的物件可回收並被 gc 的時候,短暫洩漏的那些記憶體就會被釋放。

如果程式碼沒寫好,把生命週期短的物件放到了靜態變數中,又沒有去釋放,那很遺憾,這塊記憶體就會一直被佔用,就是真正意義上的洩漏了。

舉個簡單的例子,比如把 Drawable 放到了某個類的靜態成員變數中

public void classA {
    private static Drawable sDrawable;

    public static setDrawable(Drawable drawable) {
        sDrawble = drawable;
    }
}

我們知道 Drawble 的建立是需要上下文 Context 的,而我們一般都會用當前 Activity 為 Context 傳入。像上面的程式碼,會導致整個 Activity 在業務邏輯上退出後,記憶體無法被 gc 回收。

3. 使用

最好的做法是,不要這樣寫程式碼,換個方式。可是總是有業務或者演算法實現上,就是要這樣做,比如要建立快取,又或者有個延時比較長的回撥,這時候就可以採用軟引用或者弱引用了。

  • 軟引用在手機記憶體吃緊的情況下,就會被回收
  • 弱引用是比軟引用更“弱”的引用,基本發生 gc 的時候,就會被回收

我們決定用這兩種引用來解決問題。具體使用哪種,則看業務場景的需要

  • 全域性的快取,不希望太早被 gc 的話,用軟引用合適
  • 若是一些記憶體消耗大,使用範圍小,希望儘早被回收的,用弱引用合適

但是使用的時候還是要注意,避免寫出 NullPointerException 的概率空指標程式碼,比如

public void classB {
    private final WeakReference<Activity> weakObj;

    public classB(Activity obj) {
        weabObj = new WeakReference(obj);
    }   

    void run() {
    if (weakObj.get() != null && !weakObj.get().isFinishing()) {
        ...
    }
}

為什麼會有概率空指標呢,因為弱引用的回收時候是不確定的,甚至在一兩行程式碼的執行期間都有可能發生。所以,在使用的時候,需要用一個區域性變數的強引用取出,防止中途發生 gc 而出現異常,正確的寫法

void run() {
    Activity activity = weakObj.get();
    if (activity != null && !activity.isFinishing()) {
        ...
    }
}

4. 總結

根本上,寫 Android 程式碼的時候一定要保持警覺,比如

  • 靜態成員變數
  • 非靜態內部類的使用
  • 匿名內部類的使用

這些內部類也會隱式持有當前物件的引用,有可能會發生洩漏。

而這些引用的具體場景具體分析。因為如果是一些效能差的低端機,記憶體緊張,gc 跑得很頻繁,會在當前頁面明明頁面還在執行,那些引用就被回收掉了。在表現上可能就是,使用了弱引用的回撥沒有執行,或者取到的物件為 null。

軟引用和弱引用雖然美好,但要慎用