前言
從Jdk1.2開始,在java.lang.ref包下就提供了三個(gè)類:SoftReference(軟引用),PhantomReference(虛引用)和WeakReference(弱引用),它們分別代表了系統(tǒng)對(duì)對(duì)象的中的三種引用方式:軟引用,虛引用以及弱引用。因此java語言對(duì)對(duì)象的引用有如下四種??赡苡行┕ぷ鹘?jīng)驗(yàn)比較豐富的java程序員都不太明白這幾種引用的區(qū)別,僅僅只是知道而已。但是知道弱引用和軟引用的概念以及如何使用它們是兩回事,引用類在垃圾回收工作上有著重要的作用。對(duì)于經(jīng)驗(yàn)不夠豐富的小白以及那些和我經(jīng)驗(yàn)差不多的程序員來說,他們?cè)诘谝淮温犝f這些引用類的時(shí)候,我想它們應(yīng)該和我是一樣的感覺:wtf?這是個(gè)什么東西?我怎么學(xué)了java那么久都沒聽說過?
再說說我為什么會(huì)看這個(gè)的?我司的產(chǎn)品其實(shí)前身是一個(gè)測(cè)試,我真是想破了頭,我都想不出來為什么公司會(huì)讓一個(gè)測(cè)試做產(chǎn)品。好了,言歸正傳,以下是我和我司的產(chǎn)品對(duì)話:
產(chǎn)品: 小李啊,我們這個(gè)app需要加一個(gè)讀取銀行卡的功能,你抽空做一下。具體要求是進(jìn)入這個(gè)界面的時(shí)候,用戶就可以開始讀卡了,讀卡的方法XX已經(jīng)給你封裝好了,你直接調(diào)用就可以了。
我: 好的,沒問題。大概半個(gè)小時(shí)做完。內(nèi)心竊喜,握草,這簡直是太簡單了,還好我之前做過循環(huán)輪播條的功能,還記得當(dāng)初的思路,就是用Handler發(fā)送延時(shí)消息就可以了,這個(gè)功能嘛,照葫蘆畫瓢,直接在讀卡失敗的回調(diào)接口中發(fā)消息,繼續(xù)調(diào)用讀卡方法就行了,一直讀, 直到讀卡成功后跳出循環(huán)。哎呀,我簡直是天才。bingo!搞定!
半個(gè)小時(shí)候,我司產(chǎn)品皺著眉頭走過來。。
產(chǎn)品: 哎,小李,你做的這個(gè)讀卡功能是不是有問題啊,最典型的一個(gè)問題就是,沒加這個(gè)app之前,app本身還運(yùn)行挺流暢,怎么加了這個(gè)讀卡的功能,app沒點(diǎn)進(jìn)去讀卡頁面一次,app就感覺慢一點(diǎn),你趕緊看看這個(gè)是什么原因。
我: 這怎么可能?我寫的代碼肯定是沒問題的(真是赤裸的打臉)。
半個(gè)小時(shí)后
產(chǎn)品: 怎么樣,小李,問題找到了嗎?
我: 問題找到了,我馬上解決。
究其原因就是因?yàn)樵O(shè)計(jì)是一進(jìn)來讀卡頁面就調(diào)用讀卡方法讀卡,如果讀卡失敗了,那么就會(huì)一直利用Handler發(fā)消息,而按下返回鍵的時(shí)候,由于Handler還持有讀卡頁面(Activity)的引用,導(dǎo)致讀卡頁面內(nèi)存無法回收而導(dǎo)致的內(nèi)存泄漏(OOM)問題。于是各種上google查解決辦法,其中有一種解決辦法就是利用引用去解決。而我對(duì)這方面又不是太了解,所以就花了點(diǎn)時(shí)間來了解一下,總結(jié)一下。至于Handler導(dǎo)致的內(nèi)存泄漏的原因和解決辦法,我將在另外一篇博客中詳細(xì)講解。
引用的應(yīng)用場(chǎng)景
- 我們都知道垃圾回收器會(huì)回收符合回收條件的對(duì)象的內(nèi)存,但并不是所有的程序員都知道回收條件取決于指向該對(duì)象的引用類型。這正是Java中弱引用和軟引用的主要區(qū)別。
- 如果一個(gè)對(duì)象只有弱引用指向它,垃圾回收器會(huì)立即回收該對(duì)象,這是一種急切回收方式。
- 相對(duì)的,如果有軟引用指向這些對(duì)象,則只有在JVM需要內(nèi)存時(shí)才回收這些對(duì)象。
- 弱引用和軟引用的特殊行為使得它們?cè)谀承┣闆r下非常有用。
- 例如:軟引用可以很好的用來實(shí)現(xiàn)緩存,當(dāng)JVM需要內(nèi)存時(shí),垃圾回收器就會(huì)回收這些只有被軟引用指向的對(duì)象。
- 而弱引用非常適合存儲(chǔ)元數(shù)據(jù),例如:存儲(chǔ)ClassLoader引用。如果沒有類被加載,那么也沒有指向ClassLoader的引用。一旦上一次的強(qiáng)引用被去除,只有弱引用的ClassLoader就會(huì)被回收。
強(qiáng)引用(StrongReference)
- 強(qiáng)引用是我們?cè)诰幊踢^程中使用的最簡單的引用,如代碼String s=”abc”中變量s就是字符串對(duì)象”abc”的一個(gè)強(qiáng)引用。
- 任何被強(qiáng)引用指向的對(duì)象都不能被垃圾回收器回收,這些對(duì)象都是在程序中需要的。
- StringBuffer stringBuffer = new StringBuffer(); 此時(shí)的object也是一個(gè)強(qiáng)引用。我們?cè)陂_發(fā)過程中使用的最多的就是強(qiáng)引用。這個(gè)就不做過多的說明了。
弱引用(WeakReference)
弱引用通過WeakReference類實(shí)現(xiàn),弱引用和軟引用很像但是弱引用的級(jí)別更低。
-
對(duì)于只有弱引用的對(duì)象而言,當(dāng)系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí),不管系統(tǒng)內(nèi)存是否足夠,總會(huì)回收該對(duì)象所占用的內(nèi)存(立即回收的方式)。當(dāng)然并不是說當(dāng)一個(gè)對(duì)象只有弱引用時(shí),它就會(huì)立即被回收—-正如那些使用引用的對(duì)象一樣,必須等到系統(tǒng)垃圾回收機(jī)制運(yùn)行時(shí)才會(huì)被回收。
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)建一個(gè)弱引用,讓這個(gè)弱引用引用到“Struts2權(quán)威指南”字符串 WeakReference weakReference = new WeakReference(str); //切斷str引用和“Struts2權(quán)威指南”字符串之間的引用 str = null; //取出弱引用所引用的對(duì)象 System.out.println(weakReference.get()); //強(qiáng)制進(jìn)行垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的對(duì)象 System.out.println(weakReference.get()); } } 執(zhí)行的結(jié)果為:“Struts2權(quán)威指南”和null 由上面的代碼我們可以看到,程序首先創(chuàng)建了一個(gè)“Struts2權(quán)威指南”對(duì)象,并且讓str引用變量引用它。 WeakReference weakReference = new WeakReference(str); 時(shí),系統(tǒng)首先創(chuàng)建了一個(gè)弱引用,和str指向同一個(gè)對(duì)應(yīng),當(dāng)程序執(zhí)行str = null;的時(shí)候,程序剪斷了str和“Struts2權(quán)威指南”之間的聯(lián)系,此時(shí)“Struts2權(quán)威指南”這時(shí)只有一個(gè)弱引用weakReference指向它,這個(gè)時(shí)候我們的程序依然可以通過這個(gè)弱引用來訪問該字符串常量,程序的第一個(gè)System.out.println(weakReference.get())依然是可以輸出“Struts2權(quán)威指南”的,接下來程序強(qiáng)制垃圾回收,如果系統(tǒng)垃圾回收器啟動(dòng),那么將只有弱引用所引用的對(duì)象會(huì)被清除掉,當(dāng)程序執(zhí)行到第二個(gè)System.out.println(weakReference.get())的時(shí)候,通常輸出的就只是null值了。
總結(jié): 當(dāng)我們將str賦值為空,該對(duì)象就可以被垃圾回收器回收。因?yàn)樵搶?duì)象此時(shí)不再含有其他強(qiáng)引用,即使指向該對(duì)象的弱引用WeakReference也沒有辦法阻止垃圾回收器對(duì)該對(duì)象的回收。相反的,如果該對(duì)象還有軟引用,str不會(huì)被立即回收,除非JVM需要內(nèi)存。
軟引用(SoftReference)
軟引用需要通過SoftReference類來實(shí)現(xiàn),當(dāng)一個(gè)對(duì)象只具有軟引用時(shí),它有可能被垃圾回收機(jī)制回收。對(duì)于只有軟引用的對(duì)象而言,當(dāng)系統(tǒng)內(nèi)存空間足夠時(shí),它不會(huì)被系統(tǒng)回收,程序也可使用該對(duì)象;當(dāng)系統(tǒng)內(nèi)存空間不足時(shí),系統(tǒng)將會(huì)回收它。軟引用通常用于對(duì)內(nèi)存敏感的程序中。
public class Main {
public static void main(String[] args) {
String str = new String("Struts2權(quán)威指南");
//創(chuàng)建一個(gè)軟引用,讓這個(gè)弱引用引用到“Struts權(quán)威指南”字符串
SoftReference softReference = new SoftReference(str);
//切斷str引用和“Struts2權(quán)威指南”字符串之間的引用
str = null;
//取出軟引用所引用的對(duì)象
System.out.println(softReference.get());
//強(qiáng)制進(jìn)行垃圾回收
System.gc();
System.runFinalization();
//再次取出軟引用所引用的對(duì)象
System.out.println(softReference.get());
}
}
輸出結(jié)果是:“Struts2 權(quán)威指南”和“Struts2權(quán)威指南”
- 總結(jié):軟引用的代碼執(zhí)行過程和弱引用大致相同。根據(jù)打印的內(nèi)容就說明了當(dāng)JVM內(nèi)存充足的時(shí)候,JVM是不會(huì)考慮去回收軟引用指向的內(nèi)存空間的。我們可以注意到,在上面的代碼中,有一行代碼是為str = null,雖然這行代碼是不能阻止垃圾回收器回收對(duì)象,但是可以延遲回收,這點(diǎn)和弱引用是不相同的。這也是軟引用適合做緩存而弱引用適合存儲(chǔ)元數(shù)據(jù)的根本原因所在。
- 一個(gè)使用弱引用的典型例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一種實(shí)現(xiàn)。WeakHashMap有一個(gè)特點(diǎn):map中的鍵值(keys)都被封裝成弱引用,也就是說一旦強(qiáng)引用被刪除,WeakHashMap內(nèi)部的弱引用就無法阻止該對(duì)象被垃圾回收器回收。
虛引用 (PhantomReference)
-
虛引用通過PhantomReference類實(shí)現(xiàn),虛引用完全類似于沒有引用。虛引用對(duì)對(duì)象本身沒有太大的影響,對(duì)象甚至感覺不到虛引用的存在。如果一個(gè)對(duì)象只有一個(gè)虛引用時(shí)。那它和沒有引用的效果大致相同。虛引用主要用于跟蹤對(duì)象被垃圾回收的狀態(tài),虛引用不能單獨(dú)使用,虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。
public class Main2 { public static void main(String[] args) { //創(chuàng)建一個(gè)字符串引用 String str = new String("Struts2權(quán)威指南"); //創(chuàng)建一個(gè)引用隊(duì)列 ReferenceQueue referenceQueue = new ReferenceQueue(); //創(chuàng)建一個(gè)虛引用 讓次虛引用引用到“Struts2權(quán)威指南”字符串 PhantomReference phantomReference = new PhantomReference(str, referenceQueue); //切斷str和“Struts2權(quán)威指南”之間的引用關(guān)系 str = null; //取出虛引用所引用的對(duì)象,并不能通過虛引用訪問被引用的對(duì)象, //所以此處輸出的應(yīng)該是null System.out.println(phantomReference.get()); //強(qiáng)制進(jìn)行垃圾回收 System.gc(); System.runFinalization(); //取出引用隊(duì)列最先進(jìn)入隊(duì)列中的引用于phantomReference進(jìn)行比較 System.out.println(referenceQueue.poll() == phantomReference); } } 輸出結(jié)果: null 和 true 因?yàn)橄到y(tǒng)無法通過虛引用來獲取被引用的對(duì)象,所以在執(zhí)行第一個(gè)System.out.println(phantomReference.get())的時(shí)候,打印出來的是空值(即使此時(shí)系統(tǒng)并未進(jìn)行強(qiáng)制垃圾回收)。當(dāng)程序強(qiáng)制垃圾回收后,只有虛引用引用的字符串對(duì)象將會(huì)被垃圾回收,當(dāng)被引用的對(duì)象被回收后,對(duì)象的引用將被添加到關(guān)聯(lián)的引用隊(duì)列中去(也就時(shí)說,虛引用指向的字符串對(duì)象會(huì)被垃圾回收器回收,而自己本身將被添加到與之關(guān)聯(lián)的引用隊(duì)列中去),因此我們才在第二個(gè)System.out.println(referenceQueue.poll() == phantomReference)中看到輸出的true
引用隊(duì)列(ReferenceQueue)
- 引用隊(duì)列由java.lang.ref.ReferenceQueue類來表示,它用于保存被回收后對(duì)象的引用。當(dāng)把軟引用、弱引用和引用隊(duì)列聯(lián)合使用的時(shí)候,系統(tǒng)在回收被引用的對(duì)象之后,將把被回收對(duì)象對(duì)應(yīng)的應(yīng)用添加到關(guān)聯(lián)的引用隊(duì)列中。與軟引用和弱引用不同的是,虛引用在對(duì)象被釋放之后,將把已經(jīng)回收對(duì)象對(duì)應(yīng)的虛引用添加它的關(guān)聯(lián)引用隊(duì)列中,這是得可以在對(duì)象被回收之前采取行動(dòng)。
- 軟引用和弱引用可以單獨(dú)使用,但是虛引用不能單獨(dú)使用,單獨(dú)使用虛引用沒有太大的意義。虛引用的主要作用就是跟蹤對(duì)象被垃圾回收的狀態(tài),程序可以通過檢查與虛引用關(guān)聯(lián)的引用隊(duì)列中是否已經(jīng)包含了該虛引用,從而了解虛引用所引用對(duì)象是否即將被回收。
總結(jié)
在應(yīng)用程序中,我們使用引用類可以邊面在程序執(zhí)行期間將對(duì)象留在內(nèi)存中。如果我們一軟引用,弱引用或虛引用的方式引用對(duì)象,這樣垃圾收集器就能夠隨意的釋放對(duì)象。如果希望盡可能的減少程序在其生命周期中所占的內(nèi)存大小時(shí),這些引用類就很有好處。
當(dāng)然必須指出的一個(gè)問題: 要使用這些特殊的引用類,就不能保留對(duì)對(duì)象的強(qiáng)引用。如果保留了對(duì)對(duì)象的強(qiáng)引用,那么就會(huì)浪費(fèi)這些類所提供的任何好處。
-
四種引用最主要的區(qū)別就是垃圾回收器回收的時(shí)機(jī)不同:
- 強(qiáng)引用: 我們經(jīng)常使用的一種引用?;旧侠厥掌鞑粫?huì)主動(dòng)的去回收
- 弱引用: 垃圾回收器會(huì)立刻回收弱引用。
- 軟引用: 在JVM沒有出現(xiàn)內(nèi)存不足的情況下,垃圾回收器不會(huì)去主動(dòng)回收軟引用
- 虛引用: 虛引用引用的字符串會(huì)被垃圾回收器回收, 自己本身會(huì)被添加到關(guān)聯(lián)的引用隊(duì)列中去。
以上是我自己查找資料對(duì)這方面的全部理解。如果有錯(cuò)誤,還望各位指正。希望對(duì)不了解這塊的小伙伴們有所幫助。
轉(zhuǎn)自:https://blog.csdn.net/lqw_student/article/details/52947125