1. 程式人生 > >android開發四種引用的詳解

android開發四種引用的詳解

簡述:

在android開發中,開發到一定程度的之後,就會開始關注關於APP的各種效能優化,其中很重要的一個點,就是關於四種引用的合理引用,另外在android的各種面試中,也會經常被問到關於四種引用的各種問題。

哪四種引用

四種引用分別為: 
1. 強引用(StrongRefernce) 
2. 軟引用(SoftReference) 
3. 弱引用(WeakReference) 
4. 虛引用(PhantomReference)

引用的概念理解

為了後面能夠更好的闡述四種引用,我們先講述一下關於什麼是引用

在java中,沒用指標,物件的操作全部憑藉引用來與之產生關聯,通過操作相關聯的引用,來達到操作所需物件的目的,所以,可以言簡意賅的理解為,引用,是用來操作物件的。 
例如:

User u1 = new User();
1
2
在當前的這句語句中,我們建立了一個物件User,然後這個物件會被分配到堆記憶體中的一個位置進行儲存;而區域性變數 u1被儲存在棧記憶體中。

這時,我們通過一個引用名 為u1的引用對這個User進行相關聯,通過u1我們就可以操作到這個User物件。

假如上面的那行程式碼是在一個方法體內執行的,而這時我們再寫一行這樣的程式碼

User u2 = u1;
1
那麼這時,u1持有可以操作的物件User,同時也會被u2持有。u1、u2同時關聯同一個物件。 
而在記憶體中,是這樣的 


,一個物件,是可以被多個引用持有的,而另外值得注意的一點是,並且可以是不同的引用。 這一段話為什麼要字型加粗呢,因為這對於後面各種引用回收的理解,有著重要作用。

強引用(StrongReference)

四種引用中,強引用是我們使用的最多的一種引用物件,對強應用的使用,在編碼過程中也是無處不在,例如建立一個成員變數,new 出一個物件等等……;

強引用可以直接訪問目標物件,強引用所關聯的物件,在任何時候都不會被記憶體回收,JVM寧肯丟擲OOM異常,也不會對其進行回收,所以,在通常的記憶體洩漏中,大多都有強引用的身影。

軟引用(SoftReference)

軟引用是除了硬引用之外最強的一種引用,軟引用和硬引用的不同點在於,軟引用是可被回收的;

其回收機制是:當記憶體充足的時候,在GC時,不會去回收當前的軟引用,當記憶體臨近閾值或不足的時候,在GC時,發現某一物件的引用只具有軟引用當前軟引用就會被回收。

當一物件除了具有軟引用還具有硬引用,GC時,會被回收嗎?答案是肯定不會,只會回收只具有軟引用的物件

另外值得說的一點是,以前快取圖片都會選擇使用軟引用,雖然它當記憶體不足時才回收的特點很符合作為快取,但缺點也很明顯

關於這部分快取被回收時,並沒有一套機制去進行衡量,例如先回收離上次使用間隔時長最長的,而是隨GC隨機回收;

當手機記憶體使用比較高的時候,那麼在理論上來說,GC的頻率也就會更高,需要不斷的釋放掉相應的記憶體來騰出空間,而在這個時候,也將意味著軟引用,也將會被高頻率清理,在這個過程中起到快取的實質性質效果很低,軟應用剛建立好不一會就被清理,不一會就被清理,站在效能優化的角度來說,效果並不明顯,並且在被回收後還是需要不斷重新建立,這也是消耗效能的。

所以關於圖片的快取,谷歌後來推出了LruCache,底層是LinkHashMap解決了上述的第一點問題,而可以設定儲存大小則是很好的解決了第二點問題。

弱引用(WeakReference)

弱引用是比軟引用和硬引用更弱的一種引用,在GC時,不論記憶體是否充足,發現某一物件的引用只具有弱引用當前弱引用就會被回收。

當一物件除了具有弱引用還具有硬引用,GC時,會被回收嗎?答案是肯定不會,只會回收只具有弱引用的物件

在很多時候,弱引用會被組合起來一起使用,例如其中一個使用場景:Handler的匿名內部類的實現中,可能會導致記憶體洩漏,使用靜態內部類解決持有外部引用問題,將需要的外部引用使用弱引用,而像在這部分的實際運用場景下,弱引用的使用是更符合規範,但不一定有效呢,比如弱引用持有的物件,這個物件還有硬引用,在GC時,這不符合回收規則,這個物件就不會被回收。

虛引用(PhantomReference)

虛引用不能保證其儲存物件生命週期,其儲存物件若只有虛引用,則其有效期完全隨機於GC的回收,在任何一個不確定的時間內,都可能會被回收;而虛引用與其他幾者的引用不同在於,在使用PhantomReference,必須要和Reference聯合使用。

總結與注意事項

前面對各種引用進行了詳細的解說,然後關於這幾種引用,在使用過程中,有一個細節還是非常值得注意的,軟引用,弱引用,虛引用,在建立引用物件的時候,除了傳入引用物件,通常還有一個建構函式,多了一個ReferenceQueue

        ReferenceQueue queue = new ReferenceQueue();
        WeakReference weakReference = new WeakReference(this,queue);
        SoftReference softReference = new SoftReference(this,queue);
        PhantomReference phantomReference = new PhantomReference(this,queue);
1
2
3
4
那麼這個ReferenceQueue有什麼用呢?

引用物件本身,也是一個強引用,其除了具有儲存一個物件本身特有的引用屬性之外,引用物件本身也具有java物件的一般性,那麼在其本身儲存的物件被回收之後,引用物件本身也就沒有了實用性質,需要一個適當的清理機制,來清理這些物件,避免大量這些引用物件而帶來的記憶體洩漏;這時候,就可以用到ReferenceQueue。

當引用(SoftReference/WeakReference/PhantomReference)中儲存的的物件,被GC回收時,引用本身的這個物件會被加入到ReferenceQueue中,那麼,也就是說,ReferenceQueue中儲存的物件是Reference,並且是失去了其儲存的物件的Reference。這個時候我們可以通過呼叫ReferenceQueue中提供的poll()這個API來獲取佇列中的物件,當佇列中不存在物件的時候,返回的會是null,當存在或存在多個的時候,都是返回最前面的一個Reference物件,這個時候我們就需要將這個物件進行清除,讓相應的記憶體可以被釋放掉。

Reference ref = null;
        while ((ref =  queue.poll()) != null) {
            // 清除ref
        }

1.  強引用
以前我們使用的大部分引用實際上都是強引用,這是使用最普遍的引用。如果一個物件具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足問題。

2.軟引用(SoftReference)
如果一個物件只具有軟引用,那就類似於可有可物的生活用品。如果記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。

3.弱引用(WeakReference)
如果一個物件只具有弱引用,那就類似於可有可物的生活用品。弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它 所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒, 因此不一定會很快發現那些只具有弱引用的物件。

4. 虛引用

用的比較少,就不管它了

例子

class Reference {
    private StringBuilder stringBuilder;
 
    public Reference() {
        stringBuilder = new StringBuilder("test");
    }
 
    public StringBuilder getString() {
        return stringBuilder;
    }
}

強引用舉例

Reference ref = new Reference();
Reference secondRef=ref;
ref=null;
此時Reference物件還有secodeRef強引用指向,所以即使發生GC也不會回收釋放

弱引用舉例

public static void main(String[] args) {
        System.out.println("開始");
 
        Reference ref = new Reference();
        WeakReference<Reference> weakRef = new WeakReference<Reference>(ref);
        //引用置為null,只是把引用設定為null,指向的物件是否被回收需要看情況,如果沒有任何強引用指向,
        //那麼當發生GC的時候,無論如何該物件都會被回收。(如果有軟引用指向該物件,視情況是否回收)
        ref = null; 
        
        System.gc();// 強制對系統進行GC,因為GC是不固定的,這個需要JVM排程,如果沒發生GC,那麼虛引用所指向的物件還是不會被回收,直到程式執行結束。
        // 此時該物件沒有強引用指向它,只有虛引用指向這個物件,所以可以直接回收這個物件
        // 如果此時是軟引用指向這個物件,然後發生GC,視情況決定是否回收這個物件,記憶體不足,回收,否則不回收
        // SoftReference<A> weakA = new SoftReference<A>(a);
        
        //<span style="color:#33CC00;">即使呼叫System.gc();JVM也不一定會發生回收,不同JVM有不同的實現,這裡恰好發生了GC,所以產生了如下列印結果</span>。
 
        Reference anoRef = weakRef.get();
        if (anoRef == null) {
            //說明上面new的Reference已經JVM的GC回收
            System.out.println("anoRef is null");
        } else {
            //物件沒有被回收
            System.out.println(anoRef.getString().toString());
        }
 
        System.out.println("結束");
    }

發生了GC,物件只有一個弱引用指向,所以回收釋放Reference物件
輸出:
開始
anoRef is null
結束

軟引用舉例

WeakReference<Reference> weakRef = new WeakReference<Reference>(ref);
替換為
SoftReference<A> weakA = new SoftReference<A>(a);
 
此時物件只有一個軟引用指向,因為此時記憶體還很足夠,所以不回收Reference物件

輸出:

開始

test
結束
-