程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 淺談Java中的引用,淺談Java引用

淺談Java中的引用,淺談Java引用

編輯:JAVA綜合教程

淺談Java中的引用,淺談Java引用


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

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

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

1、強引用 Strong Reference

這是Java程序中,最普遍的一種引用。

程序創建一個對象,並且把這個對象賦值給一個引用變量,我們就稱這個引用變量為強引用。很多書上說,強引用是不會被GC回收掉的,個人覺得這話是需要背景的:即強引用變量所處的位置,一定是在GC回收,所判定的Root節點能夠依次傳遞到的引用,如果出現孤立的循環引用。那麼即使對象中,存在強引用,也一定是會被回收掉的。其次需要強調的就是強引用不被回收,一定要(防盜連接:本文首發自http://www.cnblogs.com/jilodream/ )處在強引用所在的作用域中,如方法棧已經彈出,那麼棧幀中的局部變量表中的變量就會被回收,其中存在的強引用的指向關系也會被解除。當然叨叨這麼多,只是想說,強引用是否被回收,一定要看具體的情況,而不能一概而論。

筆者認為,引用,就類似於生活中對物品的持有狀態,如果一件物品,對我們至關重要,是必不可少的,無論如何打掃衛生我們都不可能會清理掉他們,那麼這種關系狀態,我們就認為是強引用。

2、軟引用 Soft Reference

當一個對象的引用關系一直保留,GC就不會清理掉這個對象,我們稱之為強引用。在平常的開發中,我們還希望有這樣一種引用狀態:只要內存夠用,即使GC進行回收,我們仍然會一直保留,反之倘若內存不夠用,那麼下次GC回收時,就會處理掉強引用所指向的對象。

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

Java的這種設計,正是為了模擬類似於生活中,對於雞肋物件的關系。如果保存空間足夠,那麼久保留該物件,如果保存空間不足,那麼才開始清理。

3、弱引用 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變量作為參數(這個以後會說)。同時方法棧幀中的局部變量表可能也會指向於堆中的其他變量,倘若直接回收,未來可能會指向無訪問權限的內存區域,導致出現內存安全問題。

4、虛引用 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不回或晚回收。強度弱的,即使對象還沒有被回收,就無法通過引用獲取到內存信息。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved