在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不回或晚回收。強度弱的,即便對象尚未被回收,就沒法經過引用獲取到內存信息。