軟引用憋活、弱引用污淋、虛引用-他們的特點及應(yīng)用場景

  • 為什么會有這4種引用

Java中的引用的定義很傳統(tǒng):如果reference類型的數(shù)據(jù)中存儲的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱這塊內(nèi)存代表著一個引用余掖。 這種定義很純粹寸爆,但是太過狹隘,一個對象在這種定義下只有被引用或者沒有被引用兩種狀態(tài)盐欺,對于如何描述一些“食之無味赁豆,棄之可惜”的對象就顯得無能為力。 我們希望能描述這樣一類對象:當內(nèi)存空間還足夠時冗美,則能保留在內(nèi)存之中魔种;如果內(nèi)存空間在進行垃圾收集后還是非常緊張,則可以拋棄這些對象粉洼。 很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場景节预。

說白了傳統(tǒng)的兩種應(yīng)用沒法描述對象生命周期中的多種狀態(tài),對象有哪些狀態(tài)呢属韧。

Java中安拟,對象的生命周期包括以下幾個階段:

  1. 創(chuàng)建階段(Created)
  2. 應(yīng)用階段(In Use)
  3. 不可見階段(Invisible)
  4. 不可達階段(Unreachable)
  5. 收集階段(Collected)
  6. 終結(jié)階段(Finalized)
  7. 對象空間重分配階段(De-allocated)

創(chuàng)建階段(Created)
在創(chuàng)建階段系統(tǒng)通過下面的幾個步驟來完成對象的創(chuàng)建過程

為對象分配存儲空間
開始構(gòu)造對象
從超類到子類對static成員進行初始化
超類成員變量按順序初始化,遞歸調(diào)用超類的構(gòu)造方法
子類成員變量按順序初始化宵喂,子類構(gòu)造方法調(diào)用
一旦對象被創(chuàng)建糠赦,并被分派給某些變量賦值,這個對象的狀態(tài)就切換到了應(yīng)用階段

應(yīng)用階段(In Use)
對象至少被一個強引用持有著锅棕。

不可見階段(Invisible)
當一個對象處于不可見階段時拙泽,說明程序本身不再持有該對象的任何強引用,雖然該這些引用仍然是存在著的裸燎。
簡單說就是程序的執(zhí)行已經(jīng)超出了該對象的作用域了顾瞻。

不可達階段(Unreachable)
對象處于不可達階段是指該對象不再被任何強引用所持有。
與“不可見階段”相比德绿,“不可見階段”是指程序不再持有該對象的任何強引用荷荤,這種情況下,該對象仍可能被JVM等系統(tǒng)下的某些已裝載的靜態(tài)變量或線程或JNI等強引用持有著脆炎,這些特殊的強引用被稱為”GC root”梅猿。存在著這些GC root會導(dǎo)致對象的內(nèi)存泄露情況氓辣,無法被回收秒裕。

收集階段(Collected)
當垃圾回收器發(fā)現(xiàn)該對象已經(jīng)處于“不可達階段”并且垃圾回收器已經(jīng)對該對象的內(nèi)存空間重新分配做好準備時,則對象進入了“收集階段”钞啸。如果該對象已經(jīng)重寫了finalize()方法几蜻,則會去執(zhí)行該方法的終端操作喇潘。
這里要特別說明一下:不要重載finazlie()方法!原因有兩點:

會影響JVM的對象分配與回收速度
在分配該對象時梭稚,JVM需要在垃圾回收器上注冊該對象颖低,以便在回收時能夠執(zhí)行該重載方法;在該方法的執(zhí)行時需要消耗CPU時間且在執(zhí)行完該方法后才會重新執(zhí)行回收操作弧烤,即至少需要垃圾回收器對該對象執(zhí)行兩次GC忱屑。

可能造成該對象的再次“復(fù)活”
在finalize()方法中,如果有其它的強引用再次持有該對象暇昂,則會導(dǎo)致對象的狀態(tài)由“收集階段”又重新變?yōu)椤皯?yīng)用階段”莺戒。這個已經(jīng)破壞了Java對象的生命周期進程,且“復(fù)活”的對象不利用后續(xù)的代碼管理急波。

終結(jié)階段
當對象執(zhí)行完finalize()方法后仍然處于不可達狀態(tài)時从铲,則該對象進入終結(jié)階段。在該階段是等待垃圾回收器對該對象空間進行回收澄暮。

對象空間重新分配階段
垃圾回收器對該對象的所占用的內(nèi)存空間進行回收或者再分配了名段,則該對象徹底消失了,稱之為“對象空間重新分配階段”泣懊。

  • 哪4種伸辟,各有什么特點
  1. 強引用

強引用是使用最普遍的引用。如果一個對象具有強引用馍刮,那垃圾收器絕不會回收它自娩。當內(nèi)存空間不足,Java虛擬機寧愿拋出OutOfM moryError錯誤渠退,使程序異常終止忙迁,也不會靠隨意回收具有強引用 對象來解決內(nèi)存不足的問題。

  1. 軟引用

軟引用是用來描述一些還有用但并非必須的對象碎乃。對于軟引用關(guān)聯(lián)著的對象姊扔,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍進行第二次回收梅誓。如果這次回收還沒有足夠的內(nèi)存恰梢,才會拋出內(nèi)存溢出異常。

/**
 * 軟引用何時被收集
 * 運行參數(shù) -Xmx200m -XX:+PrintGC
 * Created by ccr at 2018/7/14.
 */
public class SoftReferenceDemo {

    public static void main(String[] args) throws InterruptedException {
        //100M的緩存數(shù)據(jù)
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //將緩存數(shù)據(jù)用軟引用持有
        SoftReference<byte[]> cacheRef = new SoftReference<>(cacheData);
        //將緩存數(shù)據(jù)的強引用去除
        cacheData = null;
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //進行一次GC后查看對象的回收情況
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());

        //在分配一個120M的對象梗掰,看看緩存對象的回收情況
        byte[] newData = new byte[120 * 1024 * 1024];
        System.out.println("分配后" + cacheData);
        System.out.println("分配后" + cacheRef.get());
    }

}

第一次GC前null
第一次GC前[B@7d4991ad
[GC (System.gc())  105728K->103248K(175104K), 0.0009623 secs]
[Full GC (System.gc())  103248K->103139K(175104K), 0.0049909 secs]
第一次GC后null
第一次GC后[B@7d4991ad
[GC (Allocation Failure)  103805K->103171K(175104K), 0.0027889 secs]
[GC (Allocation Failure)  103171K->103171K(175104K), 0.0016018 secs]
[Full GC (Allocation Failure)  103171K->103136K(175104K), 0.0089988 secs]
[GC (Allocation Failure)  103136K->103136K(199680K), 0.0009408 secs]
[Full GC (Allocation Failure)  103136K->719K(128512K), 0.0082685 secs]
分配后null
分配后null

從上面的示例中就能看出嵌言,軟引用關(guān)聯(lián)的對象不會被GC回收。JVM在分配空間時及穗,若果Heap空間不足摧茴,就會進行相應(yīng)的GC,但是這次GC并不會收集軟引用關(guān)聯(lián)的對象埂陆,但是在JVM發(fā)現(xiàn)就算進行了一次回收后還是不足(Allocation Failure)苛白,JVM會嘗試第二次GC娃豹,回收軟引用關(guān)聯(lián)的對象。

像這種如果內(nèi)存充足购裙,GC時就保留懂版,內(nèi)存不夠,GC再來收集的功能很適合用在緩存的引用場景中躏率。在使用緩存時有一個原則躯畴,如果緩存中有就從緩存獲取,如果沒有就從數(shù)據(jù)庫中獲取薇芝,緩存的存在是為了加快計算速度私股,如果因為緩存導(dǎo)致了內(nèi)存不足進而整個程序崩潰,那就得不償失了恩掷。

  1. 弱引用

弱引用也是用來描述非必須對象的倡鲸,他的強度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象黄娘,在垃圾回收時峭状,如果這個對象只被弱引用關(guān)聯(lián)(沒有任何強引用關(guān)聯(lián)他),那么這個對象就會被回收逼争。

/**
 * 弱引用關(guān)聯(lián)對象何時被回收
 * Created by ccr at 2018/7/14.
 */
public class WeakReferenceDemo {
    public static void main(String[] args) throws InterruptedException {
        //100M的緩存數(shù)據(jù)
        byte[] cacheData = new byte[100 * 1024 * 1024];
        //將緩存數(shù)據(jù)用軟引用持有
        WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
        System.out.println("第一次GC前" + cacheData);
        System.out.println("第一次GC前" + cacheRef.get());
        //進行一次GC后查看對象的回收情況
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第一次GC后" + cacheData);
        System.out.println("第一次GC后" + cacheRef.get());

        //將緩存數(shù)據(jù)的強引用去除
        cacheData = null;
        System.gc();
        //等待GC
        Thread.sleep(500);
        System.out.println("第二次GC后" + cacheData);
        System.out.println("第二次GC后" + cacheRef.get());
    }
}
第一次GC前[B@7d4991ad
第一次GC前[B@7d4991ad
第一次GC后[B@7d4991ad
第一次GC后[B@7d4991ad
第二次GC后null
第二次GC后null

從上面的代碼中可以看出优床,弱引用關(guān)聯(lián)的對象是否回收取決于這個對象有沒有其他強引用指向它。這個確實很難理解誓焦,既然弱引用關(guān)聯(lián)對象的存活周期和強引用差不多胆敞,那直接用強引用好了,干嘛費用弄出個弱引用呢杂伟?其實弱引用存在必然有他的應(yīng)用場景移层。

static Map<Object,Object> container = new HashMap<>();
public static void putToContainer(Object key,Object value){
    container.put(key,value);
}

public static void main(String[] args) {
    //某個類中有這樣一段代碼
    Object key = new Object();
    Object value = new Object();
    putToContainer(key,value);

    //..........
    /**
     * 若干調(diào)用層次后程序員發(fā)現(xiàn)這個key指向的對象沒有用了,
     * 為了節(jié)省內(nèi)存打算把這個對象拋棄赫粥,然而下面這個方式真的能把對象回收掉嗎观话?
     * 由于container對象中包含了這個對象的引用,所以這個對象不能按照程序員的意向進行回收.
     * 并且由于在程序中的任何部分沒有再出現(xiàn)這個鍵,所以越平,這個鍵 / 值 對無法從映射中刪除频蛔。
     * 很可能會造成內(nèi)存泄漏。
     */
    key = null;
}

下面一段話摘自《Java核心技術(shù)卷1》:

設(shè)計 WeakHashMap類是為了解決一個有趣的問題秦叛。如果有一個值晦溪,對應(yīng)的鍵已經(jīng)不再 使用了, 將會出現(xiàn)什么情況呢挣跋? 假定對某個鍵的最后一次引用已經(jīng)消亡三圆,不再有任何途徑引 用這個值的對象了。但是,由于在程序中的任何部分沒有再出現(xiàn)這個鍵嫌术,所以,這個鍵 / 值 對無法從映射中刪除牌借。為什么垃圾回收器不能夠刪除它呢度气? 難道刪除無用的對象不是垃圾回 收器的工作嗎?

遺憾的是膨报,事情沒有這樣簡單磷籍。垃圾回收器跟蹤活動的對象。只要映射對象是活動的现柠, 其中的所有桶也是活動的院领, 它們不能被回收。因此够吩,需要由程序負責(zé)從長期存活的映射表中 刪除那些無用的值比然。 或者使用 WeakHashMap完成這件事情。當對鍵的唯一引用來自散列條
目時周循, 這一數(shù)據(jù)結(jié)構(gòu)將與垃圾回收器協(xié)同工作一起刪除鍵 / 值對强法。

下面是這種機制的內(nèi)部運行情況。WeakHashMap 使用弱引用(weak references) 保存鍵湾笛。 WeakReference 對象將引用保存到另外一個對象中饮怯,在這里,就是散列鍵嚎研。對于這種類型的 對象蓖墅,垃圾回收器用一種特有的方式進行處理。通常临扮,如果垃圾回收器發(fā)現(xiàn)某個特定的對象 已經(jīng)沒有他人引用了论矾,就將其回收。然而杆勇, 如果某個對象只能由 WeakReference 引用拇囊, 垃圾 回收器仍然回收它,但要將引用這個對象的弱引用放人隊列中靶橱。WeakHashMap將周期性地檢 查隊列寥袭, 以便找出新添加的弱引用。一個弱引用進人隊列意味著這個鍵不再被他人使用关霸, 并 且已經(jīng)被收集起來传黄。于是, WeakHashMap將刪除對應(yīng)的條目队寇。

除了WeakHashMap使用了弱引用膘掰,ThreadLocal類中也是用了弱引用。

  1. 虛引用

一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響识埋,也無法通過虛引用來獲取一個對象的實例凡伊。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。虛引用和弱引用對關(guān)聯(lián)對象的回收都不會產(chǎn)生影響窒舟,如果只有虛引用活著弱引用關(guān)聯(lián)著對象系忙,那么這個對象就會被回收。它們的不同之處在于弱引用的get方法惠豺,虛引用的get方法始終返回null,弱引用可以使用ReferenceQueue,虛引用必須配合ReferenceQueue使用银还。

jdk中直接內(nèi)存的回收就用到虛引用,由于jvm自動內(nèi)存管理的范圍是堆內(nèi)存洁墙,而直接內(nèi)存是在堆內(nèi)存之外(其實是內(nèi)存映射文件蛹疯,自行去理解虛擬內(nèi)存空間的相關(guān)概念),所以直接內(nèi)存的分配和回收都是有Unsafe類去操作热监,java在申請一塊直接內(nèi)存之后捺弦,會在堆內(nèi)存分配一個對象保存這個堆外內(nèi)存的引用,這個對象被垃圾收集器管理孝扛,一旦這個對象被回收羹呵,相應(yīng)的用戶線程會收到通知并對直接內(nèi)存進行清理工作。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末疗琉,一起剝皮案震驚了整個濱河市冈欢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌盈简,老刑警劉巖凑耻,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異柠贤,居然都是意外死亡香浩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門臼勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邻吭,“玉大人,你說我怎么就攤上這事宴霸〈亚纾” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵瓢谢,是天一觀的道長畸写。 經(jīng)常有香客問我,道長氓扛,這世上最難降的妖魔是什么枯芬? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上千所,老公的妹妹穿的比我還像新娘狂魔。我一直安慰自己,他們只是感情好淫痰,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布最楷。 她就那樣靜靜地躺著,像睡著了一般黑界。 火紅的嫁衣襯著肌膚如雪管嬉。 梳的紋絲不亂的頭發(fā)上皂林,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天朗鸠,我揣著相機與錄音,去河邊找鬼础倍。 笑死烛占,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的沟启。 我是一名探鬼主播忆家,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼德迹!你這毒婦竟也來了芽卿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤胳搞,失蹤者是張志新(化名)和其女友劉穎卸例,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肌毅,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡筷转,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悬而。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呜舒。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖笨奠,靈堂內(nèi)的尸體忽然破棺而出袭蝗,到底是詐尸還是另有隱情,我是刑警寧澤般婆,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布呻袭,位于F島的核電站,受9級特大地震影響腺兴,放射性物質(zhì)發(fā)生泄漏左电。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望篓足。 院中可真熱鬧段誊,春花似錦、人聲如沸栈拖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涩哟。三九已至索赏,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贴彼,已是汗流浹背潜腻。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留器仗,地道東北人融涣。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像精钮,于是被迫代替她去往敵國和親威鹿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

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