如果一個內存中的對象沒有任何引用的話,就說明這個對象已經不再被使用了,從而可以成為被垃圾回收的候選。不過由于垃圾回收器的運行時間不確定,可被垃圾回收的對象的實際被回收時間是不確定的。對于一個對象來說,只要有引用的存在,它就會一直存在于內存中。如果這樣的對象越來越多,超出了JVM中的內存總數,JVM就會拋出OutOfMemory錯誤。雖然垃圾回收的具體運行是由JVM來控制的,但是開發人員仍然可以在一定程度上與垃圾回收器進行交互,其目的在于更好的幫助垃圾回收器管理好應用的內存。這種交互方式就是使用JDK 1.2引入的java.lang.ref包。
1 強引用
強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。當內存空間不足,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
如Date date = new Date(),date就是一個對象的強引用。對象的強引用可以在程序中到處傳遞。很多情況下,會同時有多個引用指向同一個對象。強引用的存在限制了對象在內存中的存活時間。假如對象A中包含了一個對象B的強引用,那么一般情況下,對象B的存活時間就不會短于對象A。如果對象A沒有顯式的把對象B的引用設為null的話,就只有當對象A被垃圾回收之后,對象B才不再有引用指向它,才可能獲得被垃圾回收的機會。
實例代碼:
1
2
3
4
5
6
7
8
9
|
package com.skywang.java; public class StrongReferenceTest { public static void main(String[] args) { MyDate date = new MyDate(); System.gc(); } } |
運行結果:
<無任何輸出>
結果說明:即使顯式調用了垃圾回收,但是用于date是強引用,date沒有被回收。
除了強引用之外,java.lang.ref包中提供了對一個對象的不同的引用方式。JVM的垃圾回收器對于不同類型的引用有不同的處理方式。
2 軟引用
如果一個對象只具有軟引用,則內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現內存敏感的高速緩存。
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
軟引用(soft reference)在強度上弱于強引用,通過類SoftReference來表示。它的作用是告訴垃圾回收器,程序中的哪些對象是不那么重要,當內存不足的時候是可以被暫時回收的。當JVM中的內存不足的時候,垃圾回收器會釋放那些只被軟引用所指向的對象。如果全部釋放完這些對象之后,內存還不足,才會拋出OutOfMemory錯誤。軟引用非常適合于創建緩存。當系統內存不足的時候,緩存中的內容是可以被釋放的。比如考慮一個圖像編輯器的程序。該程序會把圖像文件的全部內容都讀取到內存中,以方便進行處理。而用戶也可以同時打開多個文件。當同時打開的文件過多的時候,就可能造成內存不足。如果使用軟引用來指向圖像文件內容的話,垃圾回收器就可以在必要的時候回收掉這些內存。
實例代碼:
1
2
3
4
5
6
7
8
9
10
11
|
package com.skywang.java; import java.lang.ref.SoftReference; public class SoftReferenceTest { public static void main(String[] args) { SoftReference ref = new SoftReference( new MyDate()); ReferenceTest.drainMemory(); } } |
運行結果:
<無任何輸出>
結果說明:在內存不足時,軟引用被終止。軟引用被禁止時,
1
2
|
SoftReference ref = new SoftReference( new MyDate()); ReferenceTest.drainMemory(); |
等價于
1
2
3
4
5
6
7
|
MyDate date = new MyDate(); // 由JVM決定運行 if (JVM.內存不足()) { date = null ; System.gc(); } |
3 弱引用
弱引用(weak reference)在強度上弱于軟引用,通過類WeakReference來表示。它的作用是引用一個對象,但是并不阻止該對象被回收。如果使用一個強引用的話,只要該引用存在,那么被引用的對象是不能被回收的。弱引用則沒有這個問題。在垃圾回收器運行的時候,如果一個對象的所有引用都是弱引用的話,該對象會被回收。弱引用的作用在于解決強引用所帶來的對象之間在存活時間上的耦合關系。弱引用最常見的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對象作為鍵來使用。當一個鍵值對被放入到哈希表中之后,哈希表對象本身就有了對這些鍵和值對象的引用。如果這種引用是強引用的話,那么只要哈希表對象本身還存活,其中所包含的鍵和值對象是不會被回收的。如果某個存活時間很長的哈希表中包含的鍵值對很多,最終就有可能消耗掉JVM中全部的內存。
對于這種情況的解決辦法就是使用弱引用來引用這些對象,這樣哈希表中的鍵和值對象都能被垃圾回收。Java中提供了WeakHashMap來滿足這一常見需求。
示例代碼:
1
2
3
4
5
6
7
8
9
10
11
|
package com.skywang.java; import java.lang.ref.WeakReference; public class WeakReferenceTest { public static void main(String[] args) { WeakReference ref = new WeakReference( new MyDate()); System.gc(); } } |
運行結果:
1
|
obj [Date: 1372142034360] is gc |
結果說明:在JVM垃圾回收運行時,弱引用被終止.
1
2
|
WeakReference ref = new WeakReference( new MyDate()); System.gc(); |
等同于:
1
2
3
4
5
6
7
|
MyDate date = new MyDate(); // 垃圾回收 if (JVM.內存不足()) { date = null ; System.gc(); } |
弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
4 假象引用
又叫幽靈引用~在介紹幽靈引用之前,要先介紹Java提供的對象終止化機制(finalization)。在Object類里面有個finalize方法,其設計的初衷是在一個對象被真正回收之前,可以用來執行一些清理的工作。因為Java并沒有提供類似C++的析構函數一樣的機制,就通過 finalize方法來實現。但是問題在于垃圾回收器的運行時間是不固定的,所以這些清理工作的實際運行時間也是不能預知的。幽靈引用(phantom reference)可以解決這個問題。在創建幽靈引用PhantomReference的時候必須要指定一個引用隊列。當一個對象的finalize方法已經被調用了之后,這個對象的幽靈引用會被加入到隊列中。通過檢查該隊列里面的內容就知道一個對象是不是已經準備要被回收了。
幽靈引用及其隊列的使用情況并不多見,主要用來實現比較精細的內存使用控制,這對于移動設備來說是很有意義的。程序可以在確定一個對象要被回收之后,再申請內存創建新的對象。通過這種方式可以使得程序所消耗的內存維持在一個相對較低的數量。
比如下面的代碼給出了一個緩沖區的實現示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public class PhantomBuffer { private byte [] data = new byte [ 0 ]; private ReferenceQueue< byte []> queue = new ReferenceQueue< byte []>(); private PhantomReference< byte []> ref = new PhantomReference< byte []>(data, queue); public byte [] get( int size) { if (size <= 0 ) { throw new IllegalArgumentException( "Wrong buffer size" ); } if (data.length < size) { data = null ; System.gc(); //強制運行垃圾回收器 try { queue.remove(); //該方法會阻塞直到隊列非空 ref.clear(); //幽靈引用不會自動清空,要手動運行 ref = null ; data = new byte [size]; ref = new PhantomReference< byte []>(data, queue); } catch (InterruptedException e) { e.printStackTrace(); } } return data; } } |
在上面的代碼中,每次申請新的緩沖區的時候,都首先確保之前的緩沖區的字節數組已經被成功回收。引用隊列的remove方法會阻塞直到新的幽靈引用被加入到隊列中。不過需要注意的是,這種做法會導致垃圾回收器被運行的次數過多,可能會造成程序的吞吐量過低。
示例代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com.skywang.java; import java.lang.ref.ReferenceQueue; import java.lang.ref.PhantomReference; public class PhantomReferenceTest { public static void main(String[] args) { ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference( new MyDate(), queue); System.gc(); } } |
運行結果:
1
|
obj [Date: 1372142282558] is gc |
結果說明:假象引用,在實例化后,就被終止了。
1
2
3
|
ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference( new MyDate(), queue); System.gc(); |
等同于:
1
2
|
MyDate date = new MyDate(); date = null ; |