java中的引用類似C/C++中的指針,都是指向內存中的某個對象,通過該引用可以直接操作對應的對象。引用和GC有着直接關係,根據可達性分析算法(從一個被稱爲 GC Roots 的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用),一個對象沒有引用關聯,則GC可以將其回收。
1、強引用
引用的默認方式,即是強引用,創建一個對象並把這個對象賦給一個引用變量,如Object obj = new Object();
強引用有引用變量指向時永遠不會被垃圾回收,JVM寧願拋出OutOfMemory錯誤也不會回收這種對象.
如果分配了一個大對象,如String[] strings = new String[1024 * 1024 * 600](-Xms512M -Xmx512M);jvm會拋OOM。
如果想中斷強引用和某個對象之間的關聯,需要顯式地將引用賦值爲null,如HashMap的clear操作
public void clear() {
Node<K,V>[] tab;
modCount++;
if ((tab = table) != null && size > 0) {
size = 0;
for (int i = 0; i < tab.length; ++i)
tab[i] = null;//此處顯式地將引用賦值爲null,斷開了強引用關係,便於GC
}
}
2、軟引用(SoftReference)
如果一個對象具有軟引用,內存空間足夠,垃圾回收器就不會回收它,如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用常被用作緩存,如圖片、網頁緩存等。
用法爲:
byte[] bytes = new byte[1024 * 1024 * 100];
SoftReference<byte[]> softReference = new SoftReference<>(bytes);
此時對象有兩個引用,第1行的強引用,第3行的軟引用。由於有強引用存在,對象此時是不會被回收的。
此時將bytes = null;對象只有軟引用,當內存不足時,是可以回收該對象的。
byte[] bytes = new byte[1024 * 1024 * 100];
SoftReference<byte[]> softReference = new SoftReference<>(bytes);
System.out.println(softReference.get());//[B@d716361
bytes = null;
System.gc();
System.out.println(softReference.get());//[B@d716361
byte[] bytes2 = new byte[1024 * 1024 * 300];
System.out.println(softReference.get());//null
可以看到,第10行get時還是可以取到值的,因爲此時內存充裕。當第12行需要分配一個大對象時,那對不起,要把你這個軟引用對象回收了,因爲此時內存快不夠了。垃圾收集線程會在虛擬機拋出OOM之前回收軟引用對象,而且虛擬機會儘可能優先回收長時間閒置不用的軟引用對象,對那些剛剛構建的或剛剛使用過的“新”軟引用對象會被虛擬機儘可能保留。
軟引用使用的一個例子可以參見Android的圖片緩存。
3、弱引用(WeakReference)
弱引用比軟引用優先級更低,gc時,不管內存還充不充裕,都會回收弱引用對象。
byte[] bytes = new byte[1024 * 1024 * 100];
WeakReference<byte[]> weakReference = new WeakReference<>(bytes);
bytes = null;
System.out.println(weakReference.get());//[B@d716361
System.gc();
System.out.println(weakReference.get());//null
4、虛引用(PhantomReference)
Phantom有幻覺、幽靈之意。聽名字就知道是這幾種引用中最弱的,跟不存在似的。如果對象僅僅存在虛引用,那麼GC可以隨時回收它,明白爲什麼叫Phantom了吧。
看下PhantomReference的代碼:
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
PhantomReference就一個方法get(直接返回null,所以調用了它也沒啥意義),一個構造函數(入參有個引用隊列,說明了PhantomReference必須和引用隊列一起使用)
5、引用隊列(ReferenceQueue)
查看軟引用、弱引用、虛引用的代碼可以看到,軟引用、弱引用可以和引用隊列一起使用,也可以不一起,而虛引用必須和引用隊列一起使用。那麼引用隊列是什麼?
引用隊列,顧名思義,引用組成的隊列,引用指的是Reference,SoftReference、WeakReference、PhantomReference均繼承此類,該類中有幾個屬性:
private volatile Reference<? extends T> head = null;
private T referent; /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
/* When active: NULL
* pending: this
* Enqueued: next reference in queue (or this if last)
* Inactive: this
*/
@SuppressWarnings("rawtypes")
volatile Reference next;
head是隊列維持的頭指針,referent即各自引用的對象,queue是創建引用時傳入的引用隊列,next指向下一個引用。
byte[] bytes = new byte[1024 * 1024 * 100];
ReferenceQueue<byte[]> rq = new ReferenceQueue<>();
WeakReference<byte[]> weakReference = new WeakReference<>(bytes, rq);
bytes = null;
在上面的代碼中,weakReference所引用的對象會在下次GC的時候被回收掉,自己也會被加入rq的隊列(每個引用被放進來的時候,會被加到前一個引用的前面,即Reference的next指向前一個加入進來的Reference,說是加入進隊列,其實是將隊列的head和各個Reference中的next串聯起來形成鏈表)。爲什麼要這樣做呢,weakReference引用的對象雖然在下次GC的時候被清除掉了,它自身也沒有存在的價值了,但是weakReference本身也是強引用類型啊,自身也是佔用內存的。所以weakReference被放到引用隊列的時候,我們就可以把它取出來清掉了。
ReferenceQueue提供了三種取出Reference的方法:
public Reference<? extends T> poll();//非阻塞,取出head鎖指向的Reference
public Reference<? extends T> remove();//阻塞,直到head不爲null,即隊列中有Reference
public Reference<? extends T> remove(long timeout);//阻塞,和上一個一樣,不過加了個超時時間
具體使用可以參見WeakHashMap分析。
6、總結
- 四種引用的可及性優先級:強引用>軟引用>弱引用>虛引用。當一個對象同時有多種引用類型時,可及性按高優先級處理。如
Object object = new Object();
WeakReference<Object> weakReference = new WeakReference<>(object);
同時有強引用和弱引用存在,那麼這個對象就是強可及的。GC的時候不會回收。
- 軟引用和弱引用最好和引用隊列一起使用,這樣你能知道引用對象何時被回收及做出一些相應處理。虛引用必須和引用隊列一起使用。