Java 引用類型簡述

強(qiáng)引用 ( Strong Reference )

強(qiáng)引用是使用最普遍的引用摊趾。如果一個對象具有強(qiáng)引用顽频,那垃圾回收器絕不會回收它肛著。當(dāng)內(nèi)存空間不足圆兵,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯誤,使程序異常終止枢贿,也不會靠隨意回收具有強(qiáng)引用的對象來解決內(nèi)存不足的問題殉农。 ps:強(qiáng)引用其實(shí)也就是我們平時A a = new A()這個意思。

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

Final Reference

  • 當(dāng)前類是否是finalizer類耀态,注意這里finalizer是由JVM來標(biāo)志的( 后面簡稱f類 )轮傍,并不是指java.lang.ref.Finalizer類。但是f類是會被JVM注冊到j(luò)ava.lang.ref.Finalizer類中的首装。

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

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

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

軟引用 ( Soft Reference )

是用來描述一些還有用但并非必須的對象扰柠。對于軟引用關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前疼约,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收卤档。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常程剥。
對于軟引用關(guān)聯(lián)著的對象劝枣,如果內(nèi)存充足,則垃圾回收器不會回收該對象织鲸,如果內(nèi)存不夠了舔腾,就會回收這些對象的內(nèi)存。在 JDK 1.2 之后搂擦,提供了 SoftReference 類來實(shí)現(xiàn)軟引用稳诚。軟引用可用來實(shí)現(xiàn)內(nèi)存敏感的高速緩存。軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用瀑踢,如果軟引用所引用的對象被垃圾回收器回收扳还,Java虛擬機(jī)就會把這個軟引用加入到與之關(guān)聯(lián)的引用隊列中。
注意:Java 垃圾回收器準(zhǔn)備對SoftReference所指向的對象進(jìn)行回收時橱夭,調(diào)用對象的 finalize() 方法之前普办,SoftReference對象自身會被加入到這個 ReferenceQueue 對象中,此時可以通過 ReferenceQueue 的 poll() 方法取到它們徘钥。

/**
 * 軟引用:對于軟引用關(guān)聯(lián)著的對象衔蹲,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍之中進(jìn)行第二次回收( 因為是在第一次回收后才會發(fā)現(xiàn)內(nèi)存依舊不充足,才有了這第二次回收 )舆驶。如果這次回收還沒有足夠的內(nèi)存橱健,才會拋出內(nèi)存溢出異常。
 * 對于軟引用關(guān)聯(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列入回收返回远寸,接著進(jìn)行了第三次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列入回收返回灶芝,接著進(jìn)行了第三次full gc郑原。當(dāng)?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 )

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

/**
 * 用來描述非必須的對象,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)送之前搞隐。當(dāng)垃圾收集器工作時驹愚,無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(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() 方法來取得目標(biāo)對象的強(qiáng)引用從而使用目標(biāo)對象,觀察源碼可以發(fā)現(xiàn) get() 被重寫為永遠(yuǎn)返回 null绷柒。
那虛引用到底有什么作用志于?其實(shí)虛引用主要被用來 跟蹤對象被垃圾回收的狀態(tài),通過查看引用隊列中是否包含對象所對應(yīng)的虛引用來判斷它是否 即將被垃圾回收辉巡,從而采取行動恨憎。它并不被期待用來取得目標(biāo)對象的引用,而目標(biāo)對象被回收前郊楣,它的引用會被放入一個 ReferenceQueue 對象中憔恳,從而達(dá)到跟蹤對象垃圾回收的作用。
當(dāng)phantomReference被放入隊列時净蚤,說明referent的finalize()方法已經(jīng)調(diào)用钥组,并且垃圾收集器準(zhǔn)備回收它的內(nèi)存了。
注意:PhantomReference 只有當(dāng) Java 垃圾回收器對其所指向的對象真正進(jìn)行回收時今瀑,會將其加入到這個 ReferenceQueue 對象中程梦,這樣就可以追綜對象的銷毀情況。這里referent對象的finalize()方法已經(jīng)調(diào)用過了橘荠。
所以具體用法和之前兩個有所不同屿附,它必須傳入一個 ReferenceQueue 對象。當(dāng)虛引用所引用對象準(zhǔn)備被垃圾回收時哥童,虛引用會被添加到這個隊列中挺份。
Demo1:

/**
 * 虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系贮懈。一個持有虛引用的對象匀泊,和沒有引用幾乎是一樣的,隨時都有可能被垃圾回收器回收朵你。
 * 虛引用必須和引用隊列一起使用各聘,它的作用在于跟蹤垃圾回收過程。
 * 當(dāng)phantomReference被放入隊列時抡医,說明referent的finalize()方法已經(jīng)調(diào)用躲因,并且垃圾收集器準(zhǔn)備回收它的內(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()方法箱靴,并且該方法是一個非空實(shí)現(xiàn)腺逛,所以這里MyObject也是一個Final Reference。所以第一次GC完成的是Final Reference的事情衡怀。
         * 第二次GC時棍矛,該對象(即,MyObject)對象會真正被垃圾回收器進(jìn)行回收抛杨,此時够委,將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()其實(shí)作用不大?
A:關(guān)于上面例子的問題我們要補(bǔ)充兩點(diǎn)
① 首先我們先來看下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();
    }

當(dāng)這個方法返回的時候茫船,Java虛擬機(jī)已經(jīng)盡最大努力去回收所有丟棄對象的空間了琅束。
因此不存在這System.gc()操作連續(xù)調(diào)用時,若前一次沒完成算谈,后一次可能會失效的情況涩禀。以及“所以連接調(diào)用System.gc()其實(shí)作用不大”這個說法不對,應(yīng)該說連續(xù)調(diào)用System.gc()對性能可定是有影響的然眼,但作用之一就是可以清除“漂浮垃圾”艾船。
② 同時需要特別注意的是對于已經(jīng)沒有地方引用的這些f對象,并不會在最近的那一次gc里馬上回收掉罪治,而是會延遲到下一個或者下幾個gc時才被回收丽声,因為執(zhí)行finalize方法的動作無法在gc過程中執(zhí)行礁蔗,萬一finalize方法執(zhí)行很長呢觉义,所以只能在這個gc周期里將這個垃圾對象重新標(biāo)活,直到執(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";
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洪囤,一起剝皮案震驚了整個濱河市徒坡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瘤缩,老刑警劉巖喇完,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異剥啤,居然都是意外死亡锦溪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門府怯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刻诊,“玉大人,你說我怎么就攤上這事牺丙≡蜓模” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵冲簿,是天一觀的道長粟判。 經(jīng)常有香客問我,道長峦剔,這世上最難降的妖魔是什么浮入? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮羊异,結(jié)果婚禮上事秀,老公的妹妹穿的比我還像新娘。我一直安慰自己野舶,他們只是感情好易迹,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著平道,像睡著了一般睹欲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上一屋,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天窘疮,我揣著相機(jī)與錄音,去河邊找鬼冀墨。 笑死闸衫,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诽嘉。 我是一名探鬼主播蔚出,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼弟翘,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骄酗?” 一聲冷哼從身側(cè)響起稀余,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎趋翻,沒想到半個月后睛琳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡踏烙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年掸掏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宙帝。...
    茶點(diǎn)故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡丧凤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出步脓,到底是詐尸還是另有隱情愿待,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布靴患,位于F島的核電站仍侥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸳君。R本人自食惡果不足惜农渊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望或颊。 院中可真熱鬧砸紊,春花似錦、人聲如沸囱挑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽平挑。三九已至游添,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間通熄,已是汗流浹背唆涝。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留唇辨,地道東北人廊酣。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像助泽,于是被迫代替她去往敵國和親啰扛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評論 2 349

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