前言
從Jdk1.2開始铅碍,在java.lang.ref包下就提供了三個類:SoftReference(軟引用),PhantomReference(虛引用)和WeakReference(弱引用)线椰,它們分別代表了系統(tǒng)對對象的中的三種引用方式:軟引用胞谈,虛引用以及弱引用。因此java語言對對象的引用有如下四種憨愉》成可能有些工作經(jīng)驗比較豐富的java程序員都不太明白這幾種引用的區(qū)別,僅僅只是知道而已配紫。但是知道弱引用和軟引用的概念以及如何使用它們是兩回事径密,引用類在垃圾回收工作上有著重要的作用。對于經(jīng)驗不夠豐富的小白以及那些和我經(jīng)驗差不多的程序員來說躺孝,他們在第一次聽說這些引用類的時候享扔,我想它們應(yīng)該和我是一樣的感覺:wtf?這是個什么東西括细?我怎么學(xué)了java那么久都沒聽說過伪很?
再說說我為什么會看這個的?我司的產(chǎn)品其實前身是一個測試奋单,我真是想破了頭锉试,我都想不出來為什么公司會讓一個測試做產(chǎn)品。好了览濒,言歸正傳呆盖,以下是我和我司的產(chǎn)品對話:
產(chǎn)品: 小李啊,我們這個app需要加一個讀取銀行卡的功能贷笛,你抽空做一下应又。具體要求是進入這個界面的時候,用戶就可以開始讀卡了乏苦,讀卡的方法XX已經(jīng)給你封裝好了株扛,你直接調(diào)用就可以了尤筐。
我: 好的,沒問題洞就。大概半個小時做完盆繁。內(nèi)心竊喜,握草旬蟋,這簡直是太簡單了油昂,還好我之前做過循環(huán)輪播條的功能,還記得當(dāng)初的思路倾贰,就是用Handler發(fā)送延時消息就可以了冕碟,這個功能嘛,照葫蘆畫瓢匆浙,直接在讀卡失敗的回調(diào)接口中發(fā)消息安寺,繼續(xù)調(diào)用讀卡方法就行了,一直讀首尼, 直到讀卡成功后跳出循環(huán)我衬。哎呀,我簡直是天才饰恕。bingo挠羔!搞定!
半個小時候埋嵌,我司產(chǎn)品皺著眉頭走過來破加。。
產(chǎn)品: 哎雹嗦,小李范舀,你做的這個讀卡功能是不是有問題啊,最典型的一個問題就是了罪,沒加這個app之前锭环,app本身還運行挺流暢甜刻,怎么加了這個讀卡的功能全释,app沒點進去讀卡頁面一次,app就感覺慢一點臀栈,你趕緊看看這個是什么原因娃圆。
我: 這怎么可能玫锋?我寫的代碼肯定是沒問題的(真是赤裸的打臉)。
半個小時后
產(chǎn)品: 怎么樣讼呢,小李撩鹿,問題找到了嗎?
我: 問題找到了悦屏,我馬上解決节沦。
究其原因就是因為設(shè)計是一進來讀卡頁面就調(diào)用讀卡方法讀卡键思,如果讀卡失敗了,那么就會一直利用Handler發(fā)消息甫贯,而按下返回鍵的時候稚机,由于Handler還持有讀卡頁面(Activity)的引用,導(dǎo)致讀卡頁面內(nèi)存無法回收而導(dǎo)致的內(nèi)存泄漏(OOM)問題获搏。于是各種上google查解決辦法,其中有一種解決辦法就是利用引用去解決失乾。而我對這方面又不是太了解常熙,所以就花了點時間來了解一下,總結(jié)一下碱茁。至于Handler導(dǎo)致的內(nèi)存泄漏的原因和解決辦法裸卫,我將在另外一篇博客中詳細講解。
引用的應(yīng)用場景
- 我們都知道垃圾回收器會回收符合回收條件的對象的內(nèi)存纽竣,但并不是所有的程序員都知道回收條件取決于指向該對象的引用類型墓贿。這正是Java中弱引用和軟引用的主要區(qū)別。
- 如果一個對象只有弱引用指向它蜓氨,垃圾回收器會立即回收該對象聋袋,這是一種急切回收方式。
- 相對的穴吹,如果有軟引用指向這些對象幽勒,則只有在JVM需要內(nèi)存時才回收這些對象。
- 弱引用和軟引用的特殊行為使得它們在某些情況下非常有用港令。
- 例如:軟引用可以很好的用來實現(xiàn)緩存啥容,當(dāng)JVM需要內(nèi)存時,垃圾回收器就會回收這些只有被軟引用指向的對象顷霹。
- 而弱引用非常適合存儲元數(shù)據(jù)咪惠,例如:存儲ClassLoader引用。如果沒有類被加載淋淀,那么也沒有指向ClassLoader的引用遥昧。一旦上一次的強引用被去除,只有弱引用的ClassLoader就會被回收朵纷。
強引用(StrongReference)
- 強引用是我們在編程過程中使用的最簡單的引用渠鸽,如代碼String s=”abc”中變量s就是字符串對象”abc”的一個強引用。
- 任何被強引用指向的對象都不能被垃圾回收器回收柴罐,這些對象都是在程序中需要的徽缚。
- StringBuffer stringBuffer = new StringBuffer(); 此時的object也是一個強引用。我們在開發(fā)過程中使用的最多的就是強引用革屠。這個就不做過多的說明了凿试。
弱引用(WeakReference)
弱引用通過WeakReference類實現(xiàn)排宰,弱引用和軟引用很像但是弱引用的級別更低。
-
對于只有弱引用的對象而言那婉,當(dāng)系統(tǒng)垃圾回收機制運行時板甘,不管系統(tǒng)內(nèi)存是否足夠,總會回收該對象所占用的內(nèi)存(立即回收的方式)详炬。當(dāng)然并不是說當(dāng)一個對象只有弱引用時盐类,它就會立即被回收—-正如那些使用引用的對象一樣,必須等到系統(tǒng)垃圾回收機制運行時才會被回收呛谜。
String str = new String("helloworld"); WeakReference weakReference = new WeakReference(str); str = null; 全部代碼如下: public class Main { public static void main(String[] args) { String str = new String("Struts2權(quán)威指南"); //創(chuàng)建一個弱引用在跳,讓這個弱引用引用到“Struts2權(quán)威指南”字符串 WeakReference weakReference = new WeakReference(str); //切斷str引用和“Struts2權(quán)威指南”字符串之間的引用 str = null; //取出弱引用所引用的對象 System.out.println(weakReference.get()); //強制進行垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的對象 System.out.println(weakReference.get()); } } 執(zhí)行的結(jié)果為:“Struts2權(quán)威指南”和null
由上面的代碼我們可以看到,程序首先創(chuàng)建了一個“Struts2權(quán)威指南”對象隐岛,并且讓str引用變量引用它猫妙。 WeakReference weakReference = new WeakReference(str); 時,系統(tǒng)首先創(chuàng)建了一個弱引用聚凹,和str指向同一個對應(yīng)割坠,當(dāng)程序執(zhí)行str = null;的時候,程序剪斷了str和“Struts2權(quán)威指南”之間的聯(lián)系妒牙,此時“Struts2權(quán)威指南”這時只有一個弱引用weakReference指向它彼哼,這個時候我們的程序依然可以通過這個弱引用來訪問該字符串常量,程序的第一個System.out.println(weakReference.get())依然是可以輸出“Struts2權(quán)威指南”的湘今,接下來程序強制垃圾回收沪羔,如果系統(tǒng)垃圾回收器啟動,那么將只有弱引用所引用的對象會被清除掉象浑,當(dāng)程序執(zhí)行到第二個System.out.println(weakReference.get())的時候蔫饰,通常輸出的就只是null值了。
總結(jié): 當(dāng)我們將str賦值為空愉豺,該對象就可以被垃圾回收器回收篓吁。因為該對象此時不再含有其他強引用,即使指向該對象的弱引用WeakReference也沒有辦法阻止垃圾回收器對該對象的回收蚪拦。相反的杖剪,如果該對象還有軟引用,str不會被立即回收驰贷,除非JVM需要內(nèi)存盛嘿。
軟引用(SoftReference)
軟引用需要通過SoftReference類來實現(xiàn),當(dāng)一個對象只具有軟引用時括袒,它有可能被垃圾回收機制回收次兆。對于只有軟引用的對象而言,當(dāng)系統(tǒng)內(nèi)存空間足夠時锹锰,它不會被系統(tǒng)回收芥炭,程序也可使用該對象漓库;當(dāng)系統(tǒng)內(nèi)存空間不足時,系統(tǒng)將會回收它园蝠。軟引用通常用于對內(nèi)存敏感的程序中渺蒿。
public class Main {
public static void main(String[] args) {
String str = new String("Struts2權(quán)威指南");
//創(chuàng)建一個軟引用,讓這個弱引用引用到“Struts權(quán)威指南”字符串
SoftReference softReference = new SoftReference(str);
//切斷str引用和“Struts2權(quán)威指南”字符串之間的引用
str = null;
//取出軟引用所引用的對象
System.out.println(softReference.get());
//強制進行垃圾回收
System.gc();
System.runFinalization();
//再次取出軟引用所引用的對象
System.out.println(softReference.get());
}
}
輸出結(jié)果是:“Struts2 權(quán)威指南”和“Struts2權(quán)威指南”
- 總結(jié):軟引用的代碼執(zhí)行過程和弱引用大致相同彪薛。根據(jù)打印的內(nèi)容就說明了當(dāng)JVM內(nèi)存充足的時候茂装,JVM是不會考慮去回收軟引用指向的內(nèi)存空間的。我們可以注意到善延,在上面的代碼中少态,有一行代碼是為str = null,雖然這行代碼是不能阻止垃圾回收器回收對象挚冤,但是可以延遲回收,這點和弱引用是不相同的赞庶。這也是軟引用適合做緩存而弱引用適合存儲元數(shù)據(jù)的根本原因所在训挡。
- 一個使用弱引用的典型例子是WeakHashMap,它是除HashMap和TreeMap之外歧强,Map接口的另一種實現(xiàn)澜薄。WeakHashMap有一個特點:map中的鍵值(keys)都被封裝成弱引用,也就是說一旦強引用被刪除摊册,WeakHashMap內(nèi)部的弱引用就無法阻止該對象被垃圾回收器回收肤京。
虛引用 (PhantomReference)
-
虛引用通過PhantomReference類實現(xiàn),虛引用完全類似于沒有引用茅特。虛引用對對象本身沒有太大的影響忘分,對象甚至感覺不到虛引用的存在。如果一個對象只有一個虛引用時白修。那它和沒有引用的效果大致相同妒峦。虛引用主要用于跟蹤對象被垃圾回收的狀態(tài),虛引用不能單獨使用兵睛,虛引用必須和引用隊列(ReferenceQueue)聯(lián)合使用肯骇。
public class Main2 { public static void main(String[] args) { //創(chuàng)建一個字符串引用 String str = new String("Struts2權(quán)威指南"); //創(chuàng)建一個引用隊列 ReferenceQueue referenceQueue = new ReferenceQueue(); //創(chuàng)建一個虛引用 讓次虛引用引用到“Struts2權(quán)威指南”字符串 PhantomReference phantomReference = new PhantomReference(str, referenceQueue); //切斷str和“Struts2權(quán)威指南”之間的引用關(guān)系 str = null; //取出虛引用所引用的對象,并不能通過虛引用訪問被引用的對象祖很, //所以此處輸出的應(yīng)該是null System.out.println(phantomReference.get()); //強制進行垃圾回收 System.gc(); System.runFinalization(); //取出引用隊列最先進入隊列中的引用于phantomReference進行比較 System.out.println(referenceQueue.poll() == phantomReference); } } 輸出結(jié)果: null 和 true
因為系統(tǒng)無法通過虛引用來獲取被引用的對象笛丙,所以在執(zhí)行第一個System.out.println(phantomReference.get())的時候,打印出來的是空值(即使此時系統(tǒng)并未進行強制垃圾回收)假颇。當(dāng)程序強制垃圾回收后胚鸯,只有虛引用引用的字符串對象將會被垃圾回收,當(dāng)被引用的對象被回收后笨鸡,對象的引用將被添加到關(guān)聯(lián)的引用隊列中去(也就時說蠢琳,虛引用指向的字符串對象會被垃圾回收器回收啊终,而自己本身將被添加到與之關(guān)聯(lián)的引用隊列中去),因此我們才在第二個System.out.println(referenceQueue.poll() == phantomReference)中看到輸出的true
引用隊列(ReferenceQueue)
- 引用隊列由java.lang.ref.ReferenceQueue類來表示傲须,它用于保存被回收后對象的引用蓝牲。當(dāng)把軟引用、弱引用和引用隊列聯(lián)合使用的時候泰讽,系統(tǒng)在回收被引用的對象之后例衍,將把被回收對象對應(yīng)的應(yīng)用添加到關(guān)聯(lián)的引用隊列中。與軟引用和弱引用不同的是已卸,虛引用在對象被釋放之后佛玄,將把已經(jīng)回收對象對應(yīng)的虛引用添加它的關(guān)聯(lián)引用隊列中,這是得可以在對象被回收之前采取行動累澡。
- 軟引用和弱引用可以單獨使用梦抢,但是虛引用不能單獨使用,單獨使用虛引用沒有太大的意義愧哟。虛引用的主要作用就是跟蹤對象被垃圾回收的狀態(tài)奥吩,程序可以通過檢查與虛引用關(guān)聯(lián)的引用隊列中是否已經(jīng)包含了該虛引用,從而了解虛引用所引用對象是否即將被回收蕊梧。
總結(jié)
在應(yīng)用程序中霞赫,我們使用引用類可以邊面在程序執(zhí)行期間將對象留在內(nèi)存中。如果我們一軟引用肥矢,弱引用或虛引用的方式引用對象端衰,這樣垃圾收集器就能夠隨意的釋放對象。如果希望盡可能的減少程序在其生命周期中所占的內(nèi)存大小時甘改,這些引用類就很有好處旅东。
當(dāng)然必須指出的一個問題: 要使用這些特殊的引用類,就不能保留對對象的強引用十艾。如果保留了對對象的強引用玉锌,那么就會浪費這些類所提供的任何好處。
-
四種引用最主要的區(qū)別就是垃圾回收器回收的時機不同:
- 強引用: 我們經(jīng)常使用的一種引用疟羹≈魇兀基本上垃圾回收器不會主動的去回收
- 弱引用: 垃圾回收器會立刻回收弱引用。
- 軟引用: 在JVM沒有出現(xiàn)內(nèi)存不足的情況下榄融,垃圾回收器不會去主動回收軟引用
- 虛引用: 虛引用引用的字符串會被垃圾回收器回收参淫, 自己本身會被添加到關(guān)聯(lián)的引用隊列中去。
以上是我自己查找資料對這方面的全部理解愧杯。如果有錯誤涎才,還望各位指正。希望對不了解這塊的小伙伴們有所幫助。
轉(zhuǎn)自:https://blog.csdn.net/lqw_student/article/details/52947125