1. 程式人生 > >GuavaCache學習筆記二:Java四大引用型別回顧

GuavaCache學習筆記二:Java四大引用型別回顧

前言

上一篇已經講了,如何自己實現一個LRU演算法。但是那種只是最基本的實現了LRU的剔除策略,並不能在生產中去使用。因為Guava Cache中使用的是SoftReference去做的value實現,所以有必要將Java的四種引用型別在複習一下。

備註:以下程式碼使用的JVM配置為:
-Xmx128M -Xms64M -XX:+PrintGCDetails

Java的四種引用

強引用(StrongReference)

強引用是使用最普遍的引用。如果一個物件具有強引用,那垃圾回收器絕不會回收它。如下:
Object o=new Object(); // 強引用
當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足的問題。如果不使用時,要通過如下方式來弱化引用,如下:
o=null; // 幫助垃圾收集器回收此物件
顯式地設定o為null,或超出物件的生命週期範圍,則gc認為該物件不存在引用,這時就可以回收這個物件。具體什麼時候收集這要取決於gc的演算法。

軟引用 (SoftReference)

如果一個物件只具有軟引用,則記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體。

看例子:

/**
 * @Description: 模擬Java四種引用型別的方法
 * @Author: wangmeng
 * @Date: 2018/12/8-11:10
 */
public class ReferenceExample {
    public static void main(String[] args) throws Exception{
        /**
         * SoftReference:判斷JVM快要溢位的時候,JVM GC時會判斷有沒有SoftReference資料
         */
        int counter = 0;
        List<SoftReference<Ref>> container = Lists.newArrayList();
        for (;;) {
            int current = counter++;
            container.add(new SoftReference<>(new Ref(current)));
            System.out.println("The " + current + " Ref will be insert into container");
            TimeUnit.MILLISECONDS.sleep(50);
        }
    private static class Ref {
        //呼叫Ref的時候,每次都new出來一個lM的byte,模擬觸發GC
        private byte[] data = new byte[1024 * 1024];

        private final int index;

        private Ref(int index) {
            this.index = index;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("The index [" + index + "] will be GC.");
        }
    }
}


可以看到上面的輸出結果,到了後面仍然OOM了,因為我們這裡設定的執行時間是50ms,雖然記憶體不足時進行了GC操作,但是由於放入的速度過快,所以還是OOM了。
這裡要展示的是,當記憶體不足的時候,垃圾回收就會回收軟引用的內容來防止OOM,但是這樣並不能百分百避免OOM。

弱引用(WeakReference)

弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快發現那些只具有弱引用的物件。
弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。當你想引用一個物件,但是這個物件有自己的生命週期,你不想介入這個物件的生命週期,這時候你就是用弱引用。這個引用不會在物件的垃圾回收判斷中產生任何附加的影響。

/**
 * @Description: 模擬Java四種引用型別的方法
 * @Author: wangmeng
 * @Date: 2018/12/8-11:10
 */
public class ReferenceExample {
    public static void main(String[] args) throws Exception{
        /**
         * Weak reference: 當GC的時候就會被回收
         */
        int counter = 0;
        List<WeakReference<Ref>> container = Lists.newArrayList();
        for (;;) {
            int current = counter++;
            container.add(new WeakReference<>(new Ref(current)));
            System.out.println("The " + current + " Ref will be insert into container");
            TimeUnit.MILLISECONDS.sleep(50);
        }
    }
    private static class Ref {
        //呼叫Ref的時候,每次都new出來一個lM的byte,模擬觸發GC
        private byte[] data = new byte[1024 * 1024];

        private final int index;

        private Ref(int index) {
            this.index = index;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("The index [" + index + "] will be GC.");
        }
    }
}

執行結果如圖,可見在GC執行時必定會對弱引用進行回收。

虛引用(PhantomReference)

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
虛引用主要用來跟蹤物件被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之 關聯的引用佇列中。

/**
 * @Description: 模擬Java四種引用型別的方法
 * @Author: wangmeng
 * @Date: 2018/12/8-11:10
 */
public class ReferenceExample {
    public static void main(String[] args) throws Exception{
        /**
         * PhantomReference 中有一個最佳實踐,可以通過檢視:org.apache.commons.io.FileCleaningTracker檢視
         *
         * 虛引用起到一個通知作用
         */
        Ref ref = new Ref(10);
        ReferenceQueue queue = new ReferenceQueue<>();
        MyPhantomReference reference = new MyPhantomReference(ref, queue, 10);
        ref = null;
        System.out.println(reference.get());
        System.gc();

        Reference remove = queue.remove();
        ((MyPhantomReference)remove).doAction();
    }

    private static class MyPhantomReference extends PhantomReference<Object> {
        private int index;
        public MyPhantomReference(Object referent, ReferenceQueue<? super Object> q, int index) {
            super(referent, q);
            this.index = index;
        }

        public void doAction() {
            System.out.println("The object " + index + " is GC");
        }
    }

    private static class Ref {
        //呼叫Ref的時候,每次都new出來一個lM的byte,模擬觸發GC
        private byte[] data = new byte[1024 * 1024];

        private final int index;

        private Ref(int index) {
            this.index = index;
        }

        @Override
        protected void finalize() throws Throwable {
            System.out.println("The index [" + index + "] will be GC.");
        }
    }
}

使用jconsole,點選執行GC,這時可以看到其實列印了PhantomReference中的資料。

總結

以上程式碼在我的github可以看到:
我的github地址
可參見cn.barrywangmeng.cache.reference.ReferenceExample



來自為知筆記(Wiz)