淺談Java中的引用

在Java語言中,引用是指,某一個數據,表明的是另一塊內存的的起始地址,那麼咱們就稱這個數據爲引用。java

在JVM中,GC回收的大體準則,是認定若是不能從根節點,根據引用的不斷傳遞,最終指向到一塊內存區域,咱們就將這塊內存區域回收掉。可是這樣的回收原則未免太過粗暴。有些時候,內存的使用並不緊張,咱們並不但願GC那麼勤勞的、快速的回收掉內存。反而有時候但願數據能夠在內存中儘量的保留長一會,待到虛擬機內存吃緊的時候,再來清理掉他。所以從JDK1.2以後,引用的類型變的多樣化,從而更好的適應編碼的須要。安全

下面次來介紹下四種引用:函數

一、強引用 Strong Referenceui

這是Java程序中,最廣泛的一種引用。this

程序建立一個對象,而且把這個對象賦值給一個引用變量,咱們就稱這個引用變量爲強引用。不少書上說,強引用是不會被GC回收掉的,我的以爲這話是須要背景的:即強引用變量所處的位置,必定是在GC回收,所斷定的Root節點可以依次傳遞到的引用,若是出現孤立的循環引用。那麼即便對象中,存在強引用,也必定是會被回收掉的。其次須要強調的就是強引用不被回收,必定要(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )處在強引用所在的做用域中,如方法棧已經彈出,那麼棧幀中的局部變量表中的變量就會被回收,其中存在的強引用的指向關係也會被解除。固然叨叨這麼多,只是想說,強引用是否被回收,必定要看具體的狀況,而不能一律而論。編碼

筆者認爲,引用,就相似於生活中對物品的持有狀態,若是一件物品,對咱們相當重要,是必不可少的,不管如何打掃衛生咱們都不可能會清理掉他們,那麼這種關係狀態,咱們就認爲是強引用。spa

二、軟引用 Soft Reference設計

當一個對象的引用關係一直保留,GC就不會清理掉這個對象,咱們稱之爲強引用。在日常的開發中,咱們還但願有這樣一種引用狀態:只要內存夠用,即便GC進行回收,咱們仍然會一直保留,反之假若內存不夠用,那麼下次GC回收時,就會處理掉強引用所指向的對象。code

強引用能夠理解爲GC永遠不會強制刪除的引用,而軟引用,則能夠理解爲,家中存放的無關緊要的物件,好比無關緊要的廢棄的傢俱、電腦中已經不會再使用的軟件、手機上保存的可能不會再翻閱瀏覽的信息、照片、視頻。(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )對於這些東西,只要家中仍然有剩餘的空間,手機中仍然有足夠的硬盤空間,大部分人都會一直保留,直到手機廢棄、家中拆遷。可是假若,手機的硬盤空間開始吃緊、家中沒有剩餘的空間可供使用,不少人就會選擇一次性的,把這些沒用的東西所有都處理掉,儘管這些東西可能在之前的清理過程當中,一直被保留。視頻

Java的這種設計,正是爲了模擬相似於生活中,對於雞肋物件的關係。若是保存空間足夠,那麼久保留該物件,若是保存空間不足,那麼纔開始清理。

三、弱引用 Weak Reference

軟引用讓Jvm的內存管理,擁有彈性,能夠根據使用狀況動態的調整要回收的對象。弱引用與軟引用的性質相似。不一樣之處在於,對於弱引用指向的對象,不管內存是否夠用,下次GC回收時,都會回收掉該塊內存。這就像咱們日常打掃衛生,有些東西一直在使用,可是假若要打掃衛生了,就必定會處理掉這些物品。即便房間內仍然有足夠的空間,能夠保留這些物品。

對於軟引用和弱引用,在使用時,不能夠再像原有強引用通常,直接給引用變量賦值,不然強引用關係會再次創建。這裏則須要依賴java.lang.ref包下的幾個類,使用方法能夠參考以下代碼:

 1 import java.lang.ref.SoftReference;  2 import java.lang.ref.WeakReference;  3 
 4 public class RefLearn  5 {  6     
 7     private static WeakReference<RefLearn> weakRef0;  8     private static WeakReference<RefLearn> weakRef1;  9     
10     public static void main(String arg[]) throws InterruptedException 11  { 12         RefLearn refLearn = new RefLearn(); 13  refLearn.init(); 14  } 15     
16     private void init() throws InterruptedException 17  { 18         RefLearn obj = new RefLearn(); 19         SoftReference<RefLearn> softRef = new SoftReference<RefLearn>(obj);// 將實例傳進來
20         obj = null;// 切斷原有的強引用,通常使用時,直接在構造函數中new 便可
21         weakRef0 = new WeakReference<RefLearn>(new RefLearn()); 22         weakRef1 = new WeakReference<RefLearn>(new RefLearn()); 23  softRef.get().doNothing(); 24         if (weakRef0.get() != null) 25  { 26 // System.gc(); 27 // Thread.sleep(2000);
28             Thread.sleep(1000); 29  weakRef0.get().doSomething(); 30  } 31         // obj = weakRef.get();// 這裏會再次創建強引用,會阻止回收
32  } 33     
34     private void doNothing() 35  { 36         
37  } 38     
39     private void doSomething() 40  { 41         int i = 0; 42         while (true) 43  { 44             try
45  { 46  System.gc(); 47                 Thread.sleep(1000); 48                 boolean relate0 = weakRef0.get() == null; 49                 boolean relate1 = weakRef1.get() == null; 50                 System.out.println(relate0 + " " + relate1 + " " + i++); 51  } 52             catch (InterruptedException e) 53  { 54  } 55  } 56  } 57 }

代碼中經過引用類的get()方法,能夠獲取到非強引用所指向的變量,同時使用它們。

經過上述代碼,咱們會發現兩個問題

問題一

若是內存吃緊,那麼是全部軟引用都被回收,仍是隻回收儘量少的軟引用?

答案是後者,創建軟引用後,引用對象會被打一個時間戳,標記該引用當前所處的GC時間,也就是這兩個時間:

 1     /**
 2  * Timestamp clock, updated by the garbage collector  3      */
 4    static private long clock;  5     /**
 6  * Timestamp updated by each invocation of the get method. The VM may use  7  * this field when selecting soft references to be cleared, but it is not  8  * required to do so.  9      */
10     private long timestamp;

當該軟引用每次被調用get時,都會修改該引用的時間戳,來標識該引用指向變量的最後調用時間。GC會在回收過程當中,優先回收一直不被使用的軟引用。

問題二(這是一道大題,有兩個小問(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )

(1)在判斷get()的取值是否爲null後,再次使用時,若是這個期間,內存被回收了,怎麼辦?

(2)若是在執行弱引用執行的方法時,內存是否會被回收掉?

問題(1)的狀況顯然會拋出空引用異常

所以在使用軟引用和弱引用時,務必要注意空引用異常。

問題(2)的狀況咱們能夠看示例代碼的運行結果:

經過實現能夠知道,若是正在執行一個弱引用所指向內存的方法,那麼這個虛弱引用是能夠逃過這段時間內GC的回收的。其實想一想也該明白,方法參數其實默認有this變量做爲參數(這個之後會說)。同時方法棧幀中的局部變量表可能也會指向於堆中的其餘變量,假若直接回收,將來可能會指向無訪問權限的內存區域,致使出現內存安全問題。

四、虛引用 Phantom Reference

Phantom ['fæntəm]

adj. 幽靈的;幻覺的;有名無實的

經過名稱咱們就能夠發現,這個引用的強度屬於微乎其微的。事實也的確如此。

咱們幾乎已經沒法經過虛引用,查找到任何其所指向實例的內部信息。惟一能夠得到的僅僅是該對象是否已經被回收(便是否已經通過一次GC過程)。若是非要用虛引用與現實生活中的某種聯繫相類比的話,我的以爲有點像已經丟棄到回收站中的文件,當咱們打開回收站時,是不能直接使用這些軟件的,只能判斷這些軟件有沒有被回收掉,而若是真正想再次使用這些軟件的話,須要再次創建關係性更強的引用才能夠。

虛引用與上述的其餘三個引用有比較大的區別。

咱們先來看一段代碼

 1 import java.lang.ref.PhantomReference;  2 import java.lang.ref.ReferenceQueue;  3 
 4 public class PhantomReferenceLearn  5 {  6     public void init() throws InterruptedException  7  {  8         ReferenceQueue<PhantomReferenceLearn> Refqueue = new ReferenceQueue<PhantomReferenceLearn>();  9         PhantomReference<PhantomReferenceLearn> pr = new PhantomReference<PhantomReferenceLearn>( 10                 new PhantomReferenceLearn(), Refqueue); 11  System.gc(); 12         Thread.sleep(1000); 13         boolean isClear = Refqueue.poll() == pr; 14  System.out.println(isClear); 15  } 16 }

與軟引用和弱引用不一樣的是,虛引用的構造函數只能同時伴隨着一個引用隊列來構造。當虛引用回收時,引用會被加載到構造時綁定的引用隊列中,能夠經過出隊的方式來查看引用是否已經被回收。ps.與虛引用相似,軟引用和弱引用也有相似的用法,不一樣的地方是虛引用只能這樣使用。(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )

對於虛引用的使用還有如下幾點要介紹

1)、因爲虛引用在定義時,就已經明確其不能夠經過引用關係,取出指向內存中的數據,所以儘管虛引用實例中也存有get()方法,但實際上必定返回的是null值,這點是在JDK代碼中寫死的:

註釋的意思是返回一個對象的引用,可是因爲虛引用是始終不可達的,所以始終返回null

 1     /**
 2  * Returns this reference object's referent. Because the referent of a  3  * phantom reference is always inaccessible, this method always returns  4  * <code>null</code>.  5  *  6  * @return <code>null</code>  7      */
 8     public T get() {  9         return null; 10 }

2)、虛引用的回收時機

虛引用與弱引用相似,都是在GC階段會被回收:

不一樣之處是弱引用在GC階段,一旦發現對象是弱引用,即被插入ReferenceQueue隊列中,而虛引用是在對象被銷燬後纔會被放入ReferenceQueue隊列中。

3)、虛引用的必要性

乍看起來以爲虛引用存在的必要性很是弱,他的目的就是判斷GC是否已經開始了回收,這點功能其實上弱引用徹底能夠達到。可是偏偏是虛引用的不可達性,有時是必要的。

以下面三種場景下:

(1)在處理數據時,咱們但願有些保密數據的引用是徹底切斷的,不可達的。可是咱們又但願能夠知道這些數據是否已經被回收掉,那麼這時能夠考慮虛引用。

(2)在引用變量變量被賦予新值後,咱們但願不管經過何種狀況,舊有引用指向的變量的都不被從新創建強引用(多是代碼誤操做,或者是攻擊行爲),這塊內存的數據在下次GC期間會被永久的刪除掉,這是也能夠考慮虛引用。

(3)根據引用對象被插入到引用隊列的時機,咱們但願知道對象在徹底被銷燬後的時間點。

針對於第三個用途,不少人會疑惑,知道這個時間點,咱們能有什麼用呢?(防盜鏈接:本文首發自http://www.cnblogs.com/jilodream/ )這裏先說一個題外話,搞過Java的人,基本都應該知道對象有一個和C++相似的回收方法:

protected void java.lang.Object.finalize()

這個方法須要各個須要調用的開發人員本身去複寫。爲的就是在對象析構的時刻作不少事情。可是對象從回收到調用析構方法被調用是一個很是複雜的過程(這個我之後會講),因此在不少書中都介紹,不要去複寫析構方法,如註明的《Effective Java》。可是不少時候咱們萬不得已,必需要在當前類被回收的時候作出一些行爲。這時候就能夠經過虛引用的形式,判斷當前對象是否已經被加入到引用隊列中,若是已經添加,那麼作出相應的行爲便可。

這樣基本上就把四種引用的含義和使用多介紹完了。

最後的最後,不少人會認爲引用的優先級以下:強引用>軟引用>弱引用>虛引用。

這裏我的以爲不必扯到優先級上,四種引用各有優劣,惟一的區別就是引用對象與內存之間的關係強度的大小。強度大的,可讓JVM不回或晚回收。強度弱的,即便對象尚未被回收,就沒法經過引用獲取到內存信息。

相關文章
相關標籤/搜索