1. 程式人生 > >軟引用、弱引用和虛引用處理

軟引用、弱引用和虛引用處理

前言

之前在Android上使用 Handler 引起了記憶體洩漏。從而認識了弱引用、軟引用、虛引用。今天發現Kotlin 在Android 上Anko庫裡的async, uiThread 裡面居然做了在非同步執行過程中Activity銷燬了uiThread則不會呼叫,防止記憶體洩漏。正是採用了弱引用,先溫習一下。

Java中有如下四種類型的引用:

  • 強引用(Strong Reference)
  • 軟引用(SoftReference)
  • 弱引用(WeakReference)
  • 虛引用(PhantomReference)

Java.lang.ref 是 Java 類庫中的一個包,它提供了與 Java 垃圾回收器密切相關的引用類。這些引用類物件可以指向其它物件,但它們不同於一般的引用,因為它們的存在並不防礙 Java 垃圾回收器對它們所指向的物件進行回收。其好處就在於使者可以保持對使用物件的引用,同時 JVM 依然可以在記憶體不夠用的時候對使用物件進行回收。因此這個包在用來實現與快取相關的應用時特別有用。同時該包也提供了在物件的“可達”性發生改變時,進行提醒的機制。

強引用(Strong Reference)

強引用就是我們經常使用的物件引用。 在Java中沒有相應的類與它對應 ,如果一個物件屬於強引用,就算記憶體不足時,JVM情願丟擲OOM異常使程式異常終止也不會靠回收強引用的物件來解決記憶體不足的問題。

軟引用(Soft Reference)

SoftReference 在“弱引用”中屬於最強的引用。SoftReference 所指向的物件,當沒有強引用指向它時,會在記憶體中停留一段的時間,垃圾回收器會根據 JVM 記憶體的使用情況(記憶體的緊缺程度)以及 SoftReference 的 get() 方法的呼叫情況來決定是否對其進行回收。

弱引用(Weak Reference)

WeakReference 是弱於 SoftReference 的引用型別。弱引用的特性和基本與軟引用相似,區別就在於弱引用所指向的物件只要進行系統垃圾回收,不管記憶體使用情況如何,永遠對其進行回收(get() 方法返回 null)。

虛引用 (Phantom Reference)

PhantomReference 是所有“弱引用”中最弱的引用型別。不同於軟引用和弱引用,虛引用無法通過 get() 方法來取得目標物件的強引用從而使用目標物件,觀察原始碼可以發現 get() 被重寫為永遠返回 null。

引用對列 (Reference Queue)

在適當的時候檢測到物件的可達性發生改變後,垃圾回收器就將已註冊的引用物件新增到此佇列中。一旦弱引用物件開始返回null,該弱引用指向的物件就被標記成了垃圾。而這個弱引用物件(非其指向的物件)就沒有什麼用了。通常這時候需要進行一些清理工作。比如WeakHashMap會在這時候移除沒用的條目來避免儲存無限制增長的沒有意義的弱引用。

引用型別特性總結

引用型別 取得目標物件方式 垃圾回收條件 是否可能記憶體洩漏
強引用 直接呼叫 不回收 可能
軟引用 通過 get() 方法 視記憶體情況回收 不可能
弱引用 通過 get() 方法 永遠回收 不可能
虛引用 無法取得 不回收 可能

理論到實踐->Java程式碼

public class ReferenceDemo {
    private List<Object> list = new ArrayList<>();

    public static void main(String[] args) {
        ReferenceDemo referenceDemo = new ReferenceDemo();
        //referenceDemo.strongReference();

        referenceDemo.softReference();
        referenceDemo.weakReference();
        referenceDemo.phantomReference();
    }

    private void phantomReference() {
        System.out.println("------phantomReference--------");
        ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
        PhantomReference<Object> referent = new PhantomReference<>(new Object(), refQueue);
        System.out.println(referent.get());
        System.gc();
        System.runFinalization();
        System.out.println(String.format("is recycle %s", (refQueue.poll()) == referent));
        System.out.println("------phantomReference--------");
    }

    private void weakReference() {
        list.clear();
        System.out.println("------WeakReference--------");
        for (int i = 0; i < 2; i++) {
            byte[] buff = new byte[1024 * 1024];
            WeakReference weakReference = new WeakReference(buff);
            list.add(weakReference);
        }
        list.forEach(o -> {
            Object o1 = ((WeakReference) o).get();
            System.out.println(o1);
        });
        System.out.println("------WeakReference--------");
    }

    private void strongReference() {
        byte[] buff = new byte[1024 * 1024 * 5];
    }

    private void softReference() {
        System.out.println("------softReference--------");
        for (int i = 0; i < 2; i++) {
            byte[] buff = new byte[1024 * 1024];
            //list.add(buff)
            SoftReference softReference = new SoftReference(buff);
            list.add(softReference);
        }
        list.forEach(o -> {
            Object o1 = ((SoftReference) o).get();
            System.out.println(o1);
        });
        System.out.println("------softReference--------");
    }
}

執行以上程式碼不出意料,記憶體充足時 軟、弱引用 get() 返回了不為空物件,即沒有回收

這裡寫圖片描述

接下來在intellij idea配置jvm記憶體 -Xmn3m -Xmx5m 再執行

這裡寫圖片描述

執行 byte[] buff = new byte[1024 * 1024 * 5];

這裡寫圖片描述

由上確實是JVM情願丟擲OOM異常使程式異常終止也不會靠回收強引用的物件

接下看看軟、弱、虛引用情況

這裡寫圖片描述

還沒看出結果?把迴圈+1

這裡寫圖片描述

所以得出了結論: 軟引用在記憶體緊缺時,會回收物件,而弱引用只要進行系統垃圾回收,都會回收物件。

koltlin 是基於jvm 的肯定也存在記憶體洩漏問題->Kotlin程式碼

fun main(args: Array<String>) {
    for (index in 1..3) {
        Ref().testWeakReference()
    }
}

class Ref {
    val b = ByteArray(1024 * 1024)
    fun testWeakReference() {
        Thread({
            Thread.sleep(300)
            println(b)
        }).start()
    }
}

小夥伴跑一下程式碼,便會發現OOM了,當然你還是要配置jvm堆記憶體 棧記憶體大小,儘量設小點,我這裡依然是 -Xmn3m -Xmx5m
發生了OOM 就是 println(b) 強引用著 b 物件。既然Anko庫裡的async, uiThread 裡面能防止記憶體洩漏。那我也來搞搞吧!

class Ref {
    val b = ByteArray(1024 * 1024)
    fun testWeakReference() {
        val weakReference = [email protected]Ref.weakReference()
        Thread({
            Thread.sleep(300)
            println(weakReference()?.b)
        }).start()
    }
}


class KWeakReference<T> internal constructor(any: T) {
    private val weakRef = WeakReference<T>(any)

    operator fun invoke(): T? {
        return weakRef.get()
    }
}

internal fun <T> T.weakReference() = KWeakReference(this)

執行結果:

這裡寫圖片描述

Done