WeakReference, SoftReference, ReferenceQueue學習與實驗

WeakReference, SoftReference

Java中有四種引用類型,分別是Strong, Soft, Weak, Phantom

Strong ref:強引用,被引用的對象在gc的時候不會被回收。

我們先來理解一下這句話是什麼意思。先上一小段代碼

int num = 100;
Object ref = new Object();

上面兩行代碼,第一行聲明瞭一個整型變量,它是放在程序棧上的。

它是放在程序棧上的。

這又是啥意思呢?我們給上面的代碼包在方法裏面。

public void static main(String[] args) {
    foo();
}

public void foo() {
    int num = 100;
    Object ref = new Object();
}

放在程序棧上的意思,就是int num這個變量,在foo方法執行完畢後,就因爲程序棧退棧而被銷燬了,所以使用到的內存也被回收了。

但是Object ref的情況就有些不同了。ref這個引用本身是放在程序棧上的,它也隨着foo方法執行完畢,退棧而銷燬,但是後面new Object所創建的數據,是存放在heap上的,當foo方法執行完畢後,Object對象還不一定被回收了。

在上面代碼片段中,Object這個存在於heap上的數據塊,就被ref這個引用(名詞)所引用(動詞)着。

上面的句子讀着難受,我們換個說法:Object這個存在於heap上的數據塊,就被ref這個指針所引用着。

如果把它們想象成現實世界的實物,那麼我手裏拿着ref這個對象,我就能發現它上面連着一根線,順藤摸瓜,我們就能找到一個叫Object的數據。

再回到四種引用上面來:

  • Strong ref:強引用,被引用的對象在gc的時候不會被回收。
  • Soft ref:軟引用,它引用的東西會在觸發OOM的時候釋放掉,換句話說,soft引用的東西和普通的沒區別,但是當內存緊張的時候,可能會被釋放掉。
  • Weak ref: 弱引用,當一個對象只有弱引用引用時,會在gc的時候被回收掉。
  • Phantom ref:虛引用,emm,總之就是有點玄學,還不知道具體啥用。

ReferenceQueue

這個隊列的作用,是當GC將一些引用回收的時候,將*Reference對象放到這個隊列裏面,這樣就可以通過poll方法獲取到了。

也就是說,這個對象一開始是空的,每當系統GC回收掉WeakReference或者另外兩種引用所指向的對象,就會將這個Reference對象放進這個Queue中。相當於告訴你:“我已經回收掉一些對象了,你看看需不需要做什麼特殊的操作”。

光看文檔和各種文章,其實還是一臉懵逼,於是我做了下面一個實現。

實驗

注意以下:

  • weak1是直接引用一個對象,這個對象沒有被任何強引用持有,因此按預期,一次GC後,就會被回收
  • weak2是被一個強引用持有,同時也被一個weak引用持有,按照預期,強引用一直在的話,是不會被回收的。如果它與強引用斷開關係,那麼會被回收。
  • list裏面是存放Soft引用,我會一直往裏面加數據,按照預期,當觸發GC的時候,會回收裏面的數據。
  • ReferenceQueue,我使用另一個獨立的線程一直去poll裏面的值,當有數據被回收的時候,就可以poll到一個東西。
public class Main {

    public static class Data {
        int mVar = 0;

        byte[] bitmap;

        public Data(int d) {
            mVar = d;
        }

        public Data(byte[] data) {
            bitmap = data;
        }
    }

    public static void main(String [] args) {

        ReferenceQueue<WeakReference> queue = new ReferenceQueue<>();

        WeakReference<Data> weak1 = new WeakReference(new Data(100), queue);

        Data fuck2 = new Data(200);
        WeakReference<Data> weak2 = new WeakReference(fuck2, queue);

        int i = 1;

        List<SoftReference<Data>> list = new LinkedList<>();

        // 這裏在實驗ReferenceQueue的作用
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (queue.poll() != null) {
                        System.out.println("--------------- t2: something is null");
                    }
                }
            }
        }).start();

        while (true) {

            if (i == 10) {
                fuck2 = null;
                System.out.println("let fuck2 -> null");
            }

            // 每5次,就執行一下gc
            if (i % 5 == 0) {
                Runtime.getRuntime().gc();
                System.out.println("try gc...");
            }

            // 這裏是在嘗試佔用內存,觸發soft回收
            if (i >= 15) {
                System.out.println(Runtime.getRuntime().freeMemory() + ", " + Runtime.getRuntime().maxMemory() + ", " + Runtime.getRuntime().totalMemory());
                list.add(new SoftReference(new Data(new byte[(int) (Runtime.getRuntime().freeMemory())]), queue));
                Runtime.getRuntime().gc();
            }

            // fuck1數據只有弱引用引用,所以第一次gc就會回收
            Data data = weak1.get();
            if (data == null) {
                System.out.println("fuck1 hasBeen release");
            } else {
                System.out.println("fuck1 living!");
            }

            // fuck2 有一個強引用,有一個弱引用,當第10次執行的時候,移除強引用,再gc,就會被回收
            Data dat2 = weak2.get();
            if (dat2 == null) {
                System.out.println("fuck2 hasBeen release");
            } else {
                System.out.println("fuck2 living!");
            }

            i++;
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

接下來看輸出的內容,我會將關鍵的信息用註釋放在輸出內容的後面

fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
fuck1 living!
fuck2 living!
try gc... // 首次GC
fuck1 hasBeen release // weak1 被釋放,符合預期
fuck2 living! // weak2 沒有被釋放,符合預期
--------------- t2: something is null // 新線程poll到東西了,說明剛剛有東西被釋放,符合預期
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
fuck1 hasBeen release
fuck2 living!
let fuck2 -> null // 手動接觸weak2的強引用關係
try gc...
fuck1 hasBeen release
--------------- t2: something is null // queue又poll到了,符合預期
fuck2 hasBeen release // weak2也被回收了,符合預期
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
255742864, 3817865216, 257425408 // 接下來開始一直給list加數據
fuck1 hasBeen release
fuck2 hasBeen release
255851920, 3817865216, 513277952
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 769130496
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 1024983040
fuck1 hasBeen release
fuck2 hasBeen release
255852440, 3817865216, 1280835584
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
255852536, 3817865216, 1536688128
fuck1 hasBeen release
fuck2 hasBeen release
256376632, 3817865216, 1793064960
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2049441792
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2305818624
fuck1 hasBeen release
fuck2 hasBeen release
256376728, 3817865216, 2562195456
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
256376752, 3817865216, 2818572288
fuck1 hasBeen release
fuck2 hasBeen release
123207480, 3817865216, 2941779968
--------------- t2: something is null   // 注意到這裏,終於觸發了GC,一下子poll了很多數據,說明soft裏面的引用數據都被回收了
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
fuck1 hasBeen release
fuck2 hasBeen release
160092384, 3817865216, 286261248
fuck1 hasBeen release
fuck2 hasBeen release
244181792, 3817865216, 532152320
fuck1 hasBeen release
fuck2 hasBeen release
244318104, 3817865216, 776470528
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
244318200, 3817865216, 1020788736
fuck1 hasBeen release
fuck2 hasBeen release
244842296, 3817865216, 1265631232
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 1510473728
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 1755316224
fuck1 hasBeen release
fuck2 hasBeen release
244842392, 3817865216, 2000158720
fuck1 hasBeen release
fuck2 hasBeen release
try gc...
244842488, 3817865216, 2245001216
fuck1 hasBeen release
fuck2 hasBeen release
245366584, 3817865216, 2490368000
fuck1 hasBeen release
fuck2 hasBeen release
245366680, 3817865216, 2735734784
fuck1 hasBeen release
fuck2 hasBeen release
241025560, 3817865216, 2981101568
fuck1 hasBeen release
fuck2 hasBeen release
234681496, 3817865216, 3213885440
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
--------------- t2: something is null
fuck1 hasBeen release
fuck2 hasBeen release

總結

這樣,基本就明白了WeakReference和SoftReference,以及ReferenceQueue的特性了。

WeakReference可以使用在一些observer list上,比如你的界面需要持續監聽一個事情,需要嚴格的調用成對的方法,addListener,removeListener,但是保不齊什麼時候忘記了,或者因爲一些特殊的情況,導致沒有調用remove,這樣你這個界面整個對象都被listener list一直持有了,就相當於內存泄露了。這個時候,可以使用WeakReference,這樣就算沒有remove,gc也能幹掉。

SoftReference可能在實現一些緩存上會比較有用。

ReferenceQueue相當於給我們一個能力去知道我的對象什麼時候被回收了,那麼如果需要做一些後續的事情,就可以依靠這個時機。

參考資料

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