java的四種引用

Java對引用的定義

無論是通用引用計數(shù)算法判斷對象的引用數(shù)據(jù),還是通過可達性分析算法判斷對象的引用鏈是否可達,判定對象是否存活都與“引用”有關(guān)。

在JDK1.2之前,Java中的引用定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個引用。

這種定義很純粹,但是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài),對于如何描述一些“食之無味,棄之可惜”的對象就顯得無能為力。我們希望能描述這樣一類對象:當(dāng)內(nèi)存空間還足夠時,則能保留在內(nèi)存中,如果內(nèi)存空間在進行垃圾收集后還是非常緊張,則可以拋棄這些對象。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景。

在JDK1.2之后,Java對引用的概念進行了擴充,將引用分為強引用、軟引用、弱引用、虛引用4種,這4種引用強度依次逐漸減弱。強引用就是指在程序代碼中普遍存在的,類似“Object obj=new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。

1、強引用

特點:我們平常典型編碼Object obj = new Object()中的obj就是強引用。通過關(guān)鍵字new創(chuàng)建的對象所關(guān)聯(lián)的引用就是強引用。

當(dāng)JVM內(nèi)存空間不足,JVM寧愿拋出OutOfMemoryError運行時錯誤(OOM),使程序異常終止,也不會靠隨意回收具有強引用的“存活”對象來解決內(nèi)存不足的問題。

對于一個普通的對象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強)引用賦值為 null,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略。

2、軟引用

軟引用通過SoftReference類實現(xiàn)。 軟引用的生命周期比強引用短一些。只有當(dāng)JVM認為內(nèi)存不足時,才會去試圖回收軟引用指向的對象,即JVM會確保在拋出OutOfMemoryError之前,清理軟引用指向的對象。

軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。后續(xù),我們可以調(diào)用ReferenceQueue的poll()方法來檢查是否有它所關(guān)心的對象被回收。如果隊列為空,將返回一個null,否則該方法返回隊列中前面的一個Reference對象。

應(yīng)用場景:軟引用通常用來實現(xiàn)內(nèi)存敏感的緩存。如果還有空閑內(nèi)存,就可以暫時保留緩存,當(dāng)內(nèi)存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內(nèi)存。

3、弱引用

弱引用通過WeakReference類實現(xiàn),弱引用的生命周期比軟引用短。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存。

由于垃圾回收器是一個優(yōu)先級很低的線程,因此不一定會很快回收弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關(guān)聯(lián)的引用隊列中。

應(yīng)用場景:弱應(yīng)用同樣可用于內(nèi)存敏感的緩存。

4、虛引用

虛引用也叫幻象引用,通過PhantomReference類來實現(xiàn)。

無法通過虛引用訪問對象的任何屬性或函數(shù)。幻象引用僅僅是提供了一種確保對象被 finalize 以后,做某些事情的機制。

如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準備回收一個對象時,如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之關(guān)聯(lián)的引用隊列中。

ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);

程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用,來了解被引用的對象是否將要被垃圾回收。 如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列,那么就可以在所引用的對象的內(nèi)存被回收之前采取一些程序行動。

應(yīng)用場景:可用來跟蹤對象被垃圾回收器回收的活動,當(dāng)一個虛引用關(guān)聯(lián)的對象被垃圾收集器回收之前會收到一條系統(tǒng)通知。

垃圾回收代碼測試

public class TestReference {

    private static List<Object> list = new ArrayList<>();

    public static int M = 1024 * 1024;

    public static void main(String[] args) {
        testSoftReference();
        System.out.println("------------");
        list.clear();
        testWeakReference();
        System.out.println("------------");
        testPhantomReference();
    }

    private static void testSoftReference() {
        Runtime runtime = Runtime.getRuntime();
        String value = "我是軟引用";
        SoftReference<String> sfRefer = new SoftReference<>(value);
        //可以獲得引用對象值
        System.out.println(sfRefer.get());
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[M];
            SoftReference<byte[]> sr = new SoftReference<>(buff);
            list.add(sr);
        }
        System.out.println(runtime.freeMemory() / M + "M(free) / " + runtime.maxMemory() / M + "M(max)");
        //主動觸發(fā)垃圾回收
        System.gc();
        //垃圾回收后,再查看數(shù)據(jù)
        for (int i = 0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }
        //再申請4MB的空間,觸發(fā)垃圾回收
        SoftReference<Object> softReference = new SoftReference<Object>(new byte[4 * M]);
        System.out.println("softReference.get() : " + softReference.get());
        for (int i = 0; i < list.size(); i++){
            Object obj = ((SoftReference) list.get(i)).get();
            System.out.println(obj);
        }
    }

    private static void testWeakReference() {
        Runtime runtime = Runtime.getRuntime();
        String value = "我是弱引用";
        WeakReference<String> wkRefer = new WeakReference<>(value);
        //可以獲得引用對象值
        System.out.println(wkRefer.get());
        //創(chuàng)建10MB數(shù)組
        for (int i = 0; i < 10; i++) {
            byte[] buff = new byte[M];
            WeakReference<byte[]> sr = new WeakReference<>(buff);
            list.add(sr);
        }
        System.out.println(runtime.freeMemory() / M + "M(free) / " + runtime.maxMemory() / M + "M(max)");
        //主動觸發(fā)垃圾回收
        System.gc();
        //垃圾回收后,再查看數(shù)據(jù)
        for(int i = 0; i < list.size(); i++){
            Object obj = ((WeakReference) list.get(i)).get();
            System.out.println(obj);
        }

    }

    private static void testPhantomReference() {
        // 創(chuàng)建一個字符串對象
        String str = new String("Java的4大引用");
        // 創(chuàng)建一個引用隊列
        ReferenceQueue rq = new ReferenceQueue();
        // 創(chuàng)建一個虛引用,讓此虛引用引用到"瘋狂Java講義"字符串
        PhantomReference pr = new PhantomReference (str , rq);
        // 切斷str引用和"Java的4大引用"字符串之間的引用
        str = null;
        // 取出虛引用所引用的對象,并不能通過虛引用獲取被引用的對象,所以此處輸出null
        System.out.println(pr.get());
        // 強制垃圾回收
        System.gc();
        System.runFinalization();
        // 垃圾回收之后,虛引用將被放入引用隊列中
        // 取出引用隊列中最先進入隊列中的引用與pr進行比較
        System.out.println(rq.poll() == pr);
    }
}

在筆記本上運行效果如下,可以看到默認的JVM可用內(nèi)存很大,垃圾回收時在內(nèi)存空間足夠用的情況下,軟引用對象占用的空間不會被回收。

我是軟引用
231M(free) / 3641M(max)
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
softReference.get() : [B@4dc63996
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
------------
我是弱引用
219M(free) / 3641M(max)
null
null
null
null
null
null
null
null
null
null
------------
null
true

Process finished with exit code 0

現(xiàn)在設(shè)置-Xms2M -Xmx15M,然后重新運行,效果如下:

我是軟引用
1M(free) / 14M(max)
[B@60e53b93
[B@5e2de80c
[B@1d44bcfa
[B@266474c2
[B@6f94fa3e
[B@5e481248
[B@66d3c617
[B@63947c6b
[B@2b193f2d
[B@355da254
softReference.get() : [B@4dc63996
null
null
null
null
null
null
null
null
null
null
------------
我是弱引用
2M(free) / 14M(max)
null
null
null
null
null
null
null
null
null
null
------------
null
true

Process finished with exit code 0

軟引用的實際應(yīng)用

如果一個對象只具有軟引用,那就類似于可有可無的生活用品。如果內(nèi)存空間足夠,垃圾回收器就不會回收它,如果內(nèi)存空間不足了,就會回收這些對象的內(nèi)存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存。

比如在圖片加載框架中,通過軟引用來實現(xiàn)內(nèi)存緩存。

//實現(xiàn)圖片異步加載的類
public class AsyncImageLoader {
    //以Url為鍵,SoftReference類型為值,建立緩存HashMap鍵值對。
    private Map<String, SoftReference<Drawable>> mImageCache = new HashMap<String, SoftReference<Drawable>>();
     
    //實現(xiàn)圖片異步加載
    public Drawable loadDrawable(final String imageUrl, final ImageCallback callback) {
        //查詢緩存,查看當(dāng)前需要下載的圖片是否在緩存中
        if(mImageCache.containsKey(imageUrl)) {
            SoftReference<Drawable> softReference = mImageCache.get(imageUrl);
            if (softReference.get() != null) {
                return softReference.get();
            }
        }
         
        final Handler handler = new Handler() {
            @Override
            public void dispatchMessage(Message msg) {
                //回調(diào)ImageCallbackImpl中的imageLoad方法,在主線(UI線程)中執(zhí)行。
                callback.imageLoad((Drawable)msg.obj);
            }
        };
         
        /*若緩存中沒有,新開辟一個線程,用于進行從網(wǎng)絡(luò)上下載圖片,
         * 然后將獲取到的Drawable發(fā)送到Handler中處理,通過回調(diào)實現(xiàn)在UI線程中顯示獲取的圖片
         */
        new Thread() {      
            public void run() {
                Drawable drawable = loadImageFromUrl(imageUrl);
                //將得到的圖片存放到緩存中
                mImageCache.put(imageUrl, new SoftReference<Drawable>(drawable));
                Message message = handler.obtainMessage(0, drawable);
                handler.sendMessage(message);
            };
        }.start();
         
        //若緩存中不存在,將從網(wǎng)上下載顯示完成后,此處返回null;
        return null;
    }
     
    //定義一個回調(diào)接口
    public interface ImageCallback {
        void imageLoad(Drawable drawable);
    }
     
    //通過Url從網(wǎng)上獲取圖片Drawable對象;
    protected Drawable loadImageFromUrl(String imageUrl) {
        try {
            return Drawable.createFromStream(new URL(imageUrl).openStream(),"debug");
        } catch (Exception e) {
            // TODO: handle exception
            throw new RuntimeException(e);
        }
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容