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