Java 引用類型簡述

強引用 ( Strong Reference )

強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內(nèi)存不足的問題。 ps:強引用其實也就是我們平時A a = new A()這個意思。

  • 強引用特性
    • 強引用可以直接訪問目標對象。
    • 強引用所指向的對象在任何時候都不會被系統(tǒng)回收。
    • 強引用可能導致內(nèi)存泄漏。

Final Reference

  • 當前類是否是finalizer類,注意這里finalizer是由JVM來標志的( 后面簡稱f類 ),并不是指java.lang.ref.Finalizer類。但是f類是會被JVM注冊到java.lang.ref.Finalizer類中的。

① 當前類或父類中含有一個參數(shù)為空,返回值為void的名為finalize的方法。
② 并且該finalize方法必須非空

  • GC 回收問題
    • 對象因為Finalizer的引用而變成了一個臨時的強引用,即使沒有其他的強引用,還是無法立即被回收;
    • 對象至少經(jīng)歷兩次GC才能被回收,因為只有在FinalizerThread執(zhí)行完了f對象的finalize方法的情況下才有可能被下次GC回收,而有可能期間已經(jīng)經(jīng)歷過多次GC了,但是一直還沒執(zhí)行對象的finalize方法;
    • CPU資源比較稀缺的情況下FinalizerThread線程有可能因為優(yōu)先級比較低而延遲執(zhí)行對象的finalize方法;
    • 因為對象的finalize方法遲遲沒有執(zhí)行,有可能會導致大部分f對象進入到old分代,此時容易引發(fā)old分代的GC,甚至Full GC,GC暫停時間明顯變長,甚至導致OOM;
    • 對象的finalize方法被調(diào)用后,這個對象其實還并沒有被回收,雖然可能在不久的將來會被回收。

詳見:JVM源碼分析之FinalReference完全解讀 - 你假笨

軟引用 ( Soft Reference )

是用來描述一些還有用但并非必須的對象。對于軟引用關聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。
對于軟引用關聯(lián)著的對象,如果內(nèi)存充足,則垃圾回收器不會回收該對象,如果內(nèi)存不夠了,就會回收這些對象的內(nèi)存。在 JDK 1.2 之后,提供了 SoftReference 類來實現(xiàn)軟引用。軟引用可用來實現(xiàn)內(nèi)存敏感的高速緩存。軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列中。
注意:Java 垃圾回收器準備對SoftReference所指向的對象進行回收時,調(diào)用對象的 finalize() 方法之前,SoftReference對象自身會被加入到這個 ReferenceQueue 對象中,此時可以通過 ReferenceQueue 的 poll() 方法取到它們。

/**
 * 軟引用:對于軟引用關聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收( 因為是在第一次回收后才會發(fā)現(xiàn)內(nèi)存依舊不充足,才有了這第二次回收 )。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。
 * 對于軟引用關聯(lián)著的對象,如果內(nèi)存充足,則垃圾回收器不會回收該對象,如果內(nèi)存不夠了,就會回收這些對象的內(nèi)存。
 * 通過debug發(fā)現(xiàn),軟引用在pending狀態(tài)時,referent就已經(jīng)是null了。
 *
 * 啟動參數(shù):-Xmx5m
 *
 */
public class SoftReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) throws InterruptedException {
        Thread.sleep(3000);
        MyObject object = new MyObject();
        SoftReference<MyObject> softRef = new SoftReference(object, queue);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.gc();
        System.out.println("After GC : Soft Get = " + softRef.get());
        System.out.println("分配大塊內(nèi)存");

        /**
         * ====================== 控制臺打印 ======================
         * After GC : Soft Get = I am MyObject.
         * 分配大塊內(nèi)存
         * MyObject's finalize called
         * Object for softReference is null
         * After new byte[] : Soft Get = null
         * ====================== 控制臺打印 ======================
         *
         * 總共觸發(fā)了 3 次 full gc。第一次有System.gc();觸發(fā);第二次在在分配new byte[5*1024*740]時觸發(fā),然后發(fā)現(xiàn)內(nèi)存不夠,于是將softRef列入回收返回,接著進行了第三次full gc。
         */
//        byte[] b = new byte[5*1024*740];

        /**
         * ====================== 控制臺打印 ======================
         * After GC : Soft Get = I am MyObject.
         * 分配大塊內(nèi)存
         * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
         *      at com.bayern.multi_thread.part5.SoftReferenceDemo.main(SoftReferenceDemo.java:21)
         * MyObject's finalize called
         * Object for softReference is null
         * ====================== 控制臺打印 ======================
         *
         * 也是觸發(fā)了 3 次 full gc。第一次有System.gc();觸發(fā);第二次在在分配new byte[5*1024*740]時觸發(fā),然后發(fā)現(xiàn)內(nèi)存不夠,于是將softRef列入回收返回,接著進行了第三次full gc。當?shù)谌?full gc 后發(fā)現(xiàn)內(nèi)存依舊不夠用于分配new byte[5*1024*740],則就拋出了OutOfMemoryError異常。
         */
        byte[] b = new byte[5*1024*790];

        System.out.println("After new byte[] : Soft Get = " + softRef.get());
    }

    public static class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>) queue.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (obj != null) {
                System.out.println("Object for softReference is " + obj.get());
            }

        }
    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject.";
        }
    }
}


弱引用 ( Weak Reference )

用來描述非必須的對象,但是它的強度比軟引用更弱一些,被弱引用關聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當垃圾收集器工作時,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關聯(lián)的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個注冊引用隊列中。
注意:Java 垃圾回收器準備對WeakReference所指向的對象進行回收時,調(diào)用對象的 finalize() 方法之前,WeakReference對象自身會被加入到這個 ReferenceQueue 對象中,此時可以通過 ReferenceQueue 的 poll() 方法取到它們。

/**
 * 用來描述非必須的對象,但是它的強度比軟引用更弱一些,被弱引用關聯(lián)的對象只能生存到下一次垃圾收集發(fā)送之前。當垃圾收集器工作時,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關聯(lián)的對象。一旦一個弱引用對象被垃圾回收器回收,便會加入到一個注冊引用隊列中。
 */
public class WeakReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) {

        MyObject object = new MyObject();
        Reference<MyObject> weakRef = new WeakReference<>(object, queue);
        System.out.println("創(chuàng)建的弱引用為 : " + weakRef);
        new Thread(new CheckRefQueue()).start();

        object = null;
        System.out.println("Before GC: Weak Get = " + weakRef.get());
        System.gc();
        System.out.println("After GC: Weak Get = " + weakRef.get());

        /**
         * ====================== 控制臺打印 ======================
         * 創(chuàng)建的弱引用為 : java.lang.ref.WeakReference@1d44bcfa
         * Before GC: Weak Get = I am MyObject
         * After GC: Weak Get = null
         * MyObject's finalize called
         * 刪除的弱引用為 : java.lang.ref.WeakReference@1d44bcfa , 獲取到的弱引用的對象為 : null
         * ====================== 控制臺打印 ======================
         */
    }

    public static class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>)queue.remove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(obj != null) {
                System.out.println("刪除的弱引用為 : " + obj + " , 獲取到的弱引用的對象為 : " + obj.get());

            }

        }
    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }
}


虛引用 ( Phantom Reference )

PhantomReference 是所有“弱引用”中最弱的引用類型。不同于軟引用和弱引用,虛引用無法通過 get() 方法來取得目標對象的強引用從而使用目標對象,觀察源碼可以發(fā)現(xiàn) get() 被重寫為永遠返回 null。
那虛引用到底有什么作用?其實虛引用主要被用來 跟蹤對象被垃圾回收的狀態(tài),通過查看引用隊列中是否包含對象所對應的虛引用來判斷它是否 即將被垃圾回收,從而采取行動。它并不被期待用來取得目標對象的引用,而目標對象被回收前,它的引用會被放入一個 ReferenceQueue 對象中,從而達到跟蹤對象垃圾回收的作用。
當phantomReference被放入隊列時,說明referent的finalize()方法已經(jīng)調(diào)用,并且垃圾收集器準備回收它的內(nèi)存了。
注意:PhantomReference 只有當 Java 垃圾回收器對其所指向的對象真正進行回收時,會將其加入到這個 ReferenceQueue 對象中,這樣就可以追綜對象的銷毀情況。這里referent對象的finalize()方法已經(jīng)調(diào)用過了。
所以具體用法和之前兩個有所不同,它必須傳入一個 ReferenceQueue 對象。當虛引用所引用對象準備被垃圾回收時,虛引用會被添加到這個隊列中。
Demo1:

/**
 * 虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個持有虛引用的對象,和沒有引用幾乎是一樣的,隨時都有可能被垃圾回收器回收。
 * 虛引用必須和引用隊列一起使用,它的作用在于跟蹤垃圾回收過程。
 * 當phantomReference被放入隊列時,說明referent的finalize()方法已經(jīng)調(diào)用,并且垃圾收集器準備回收它的內(nèi)存了。
 */
public class PhantomReferenceDemo {

    private static ReferenceQueue<MyObject> queue = new ReferenceQueue<>();

    public static void main(String[] args) throws InterruptedException {
        MyObject object = new MyObject();
        Reference<MyObject> phanRef = new PhantomReference<>(object, queue);
        System.out.println("創(chuàng)建的虛擬引用為 : " + phanRef);
        new Thread(new CheckRefQueue()).start();

        object = null;

        int i = 1;
        while (true) {
            System.out.println("第" + i++ + "次GC");
            System.gc();
            TimeUnit.SECONDS.sleep(1);
        }

        /**
         * ====================== 控制臺打印 ======================
         * 創(chuàng)建的虛擬引用為 : java.lang.ref.PhantomReference@1d44bcfa
         * 第1次GC
         * MyObject's finalize called
         * 第2次GC
         * 刪除的虛引用為: java.lang.ref.PhantomReference@1d44bcfa , 獲取虛引用的對象 : null
         * ====================== 控制臺打印 ======================
         *
         * 再經(jīng)過一次GC之后,系統(tǒng)找到了垃圾對象,并調(diào)用finalize()方法回收內(nèi)存,但沒有立即加入PhantomReference Queue中。因為MyObject對象重寫了finalize()方法,并且該方法是一個非空實現(xiàn),所以這里MyObject也是一個Final Reference。所以第一次GC完成的是Final Reference的事情。
         * 第二次GC時,該對象(即,MyObject)對象會真正被垃圾回收器進行回收,此時,將PhantomReference加入虛引用隊列( PhantomReference Queue )。
         * 而且每次gc之間需要停頓一些時間,已給JVM足夠的處理時間;如果這里沒有TimeUnit.SECONDS.sleep(1); 可能需要gc到第5、6次才會成功。
         */

    }

    public static class MyObject {

        @Override
        protected void finalize() throws Throwable {
            System.out.println("MyObject's finalize called");
            super.finalize();
        }

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }

    public static  class CheckRefQueue implements Runnable {

        Reference<MyObject> obj = null;

        @Override
        public void run() {
            try {
                obj = (Reference<MyObject>)queue.remove();
                System.out.println("刪除的虛引用為: " + obj + " , 獲取虛引用的對象 : " + obj.get());
                System.exit(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Q:??了解下System.gc()操作,如果連續(xù)調(diào)用,若前一次沒完成,后一次可能會失效,所以連接調(diào)用System.gc()其實作用不大?
A:關于上面例子的問題我們要補充兩點
① 首先我們先來看下System.gc()的doc文檔:

    /**
     * Runs the garbage collector.
     * <p>
     * Calling the <code>gc</code> method suggests that the Java Virtual
     * Machine expend effort toward recycling unused objects in order to
     * make the memory they currently occupy available for quick reuse.
     * When control returns from the method call, the Java Virtual
     * Machine has made a best effort to reclaim space from all discarded
     * objects.
     * <p>
     * The call <code>System.gc()</code> is effectively equivalent to the
     * call:
     * <blockquote><pre>
     * Runtime.getRuntime().gc()
     * </pre></blockquote>
     *
     * @see     java.lang.Runtime#gc()
     */
    public static void gc() {
        Runtime.getRuntime().gc();
    }

當這個方法返回的時候,Java虛擬機已經(jīng)盡最大努力去回收所有丟棄對象的空間了。
因此不存在這System.gc()操作連續(xù)調(diào)用時,若前一次沒完成,后一次可能會失效的情況。以及“所以連接調(diào)用System.gc()其實作用不大”這個說法不對,應該說連續(xù)調(diào)用System.gc()對性能可定是有影響的,但作用之一就是可以清除“漂浮垃圾”。
② 同時需要特別注意的是對于已經(jīng)沒有地方引用的這些f對象,并不會在最近的那一次gc里馬上回收掉,而是會延遲到下一個或者下幾個gc時才被回收,因為執(zhí)行finalize方法的動作無法在gc過程中執(zhí)行,萬一finalize方法執(zhí)行很長呢,所以只能在這個gc周期里將這個垃圾對象重新標活,直到執(zhí)行完finalize方法將Final Reference從queue里刪除,這樣下次gc的時候就真的是漂浮垃圾了會被回收。

Demo2:

public class PhantomReferenceDemo2 {

    public static void main(String[] args) {
        ReferenceQueue<MyObject> queue = new ReferenceQueue<>();
        MyObject object = new MyObject();
        Reference<MyObject> phanRef = new PhantomReference<>(object, queue);
        System.out.println("創(chuàng)建的虛擬引用為 : " + phanRef);
        object = null;
        System.out.println(phanRef.get());

        System.gc();

        System.out.println("referent : " + phanRef);
        System.out.println(queue.poll() == phanRef); //true

        /**
         * ====================== 控制臺打印 ======================
         * 創(chuàng)建的虛擬引用為 : java.lang.ref.PhantomReference@1d44bcfa
         * null
         * referent : java.lang.ref.PhantomReference@1d44bcfa
         * true
         * ====================== 控制臺打印 ======================
         *
         * 這里因為MyObject沒有重寫finalize()方法,所以這里的在System.gc()后就會處理PhantomReference加入到PhantomReference Queue中。
         */
    }

    public static class MyObject {

        @Override
        public String toString() {
            return "I am MyObject";
        }
    }
}
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容