Java中提供了一個Reference抽象類,此類定義所有引用對象共有的操作,與垃圾收集器密切配合實現的。主要是為了決定某些對象的生命周期,有利于JVM進行垃圾回收。而繼承此類的有四種引用,分別是StrongReference(強引用),SoftReference(軟引用),WeakReference(弱引用),PhantomReference(虛引用),強度按照上面的順序依次減弱。下面來看下四種引用的對比。
類型 | 調用方式 | 回收條件 | 內存泄漏 |
---|---|---|---|
StrongReference | 直接調用 | 不回收 | 可能 |
StrongReference | get()方法 | 視內存情況回收 | 不可能 |
WeakReference | get()方法 | 永遠回收 | 不可能 |
PhantomReference | 無法取得 | 不回收 | 可能 |
- 強引用
Object object = new Object()
上面這段代碼就是一個強引用,是最普通的引用,當內存空間不足, Java 虛擬機寧愿拋出 OutOfMemoryError 錯誤,使程序異常終止, 也不會靠隨意回收具有強引用的對象來解決內存不足的問。如果想中斷或者回收強引用,可以設置引用為null,如object =null,這樣的話JVM就會在合適的時間,進行垃圾回收。可以看下下面代碼和運行情況。
private static void strongTest() {
printlnMemory("Init");
// 申請5MB的內存
byte[] strong = new byte[5 * MB];
printlnMemory("Use 5MB");
// 回收
System.gc();
printlnMemory("GC after");
System.out.println("gc strong:" + strong);
// 設置引用為null
strong = null;
printlnMemory("set null");
System.out.println("set null strong:" + strong);
// 回收
System.gc();
printlnMemory("null GC after");
System.out.println("gc strong:" + strong);
}
運行情況:
Init:240M(free)/245M(total)
Use 5MB:235M(free)/245M(total)// 使用了5MB內存
GC after:237M(free)/245M(total)// 釋放一些內存
gc strong:[B@7ea987ac
set null:237M(free)/245M(total)// 強引用設置為null后,內存不變
set null strong:null
null GC after:242M(free)/245M(total)//強引用設置為null后,回收5MB內存
gc strong:null
- 軟引用
SoftReference<Object> soft = new SoftReference(new Object());
若一個對象只有軟引用,則當空間不足的時候才會回收它,可以用來構建敏感數據的緩存(如網頁緩存、圖片緩存等)。軟引用可以和一個引用隊列一同使用,當所引用的對象被回收,軟引用便被加入到引用隊列。可以看下下面代碼和運行情況。
private static void softTest() {
printlnMemory("Init");
SoftReference<byte[]> soft = new SoftReference<>(new byte[2000 * MB]);// 申請2000MB的內存
printlnMemory("Use 2000MB");
System.gc();// gc回收
printlnMemory("GC after");
System.out.println("gc soft:" + soft.get());
SoftReference<byte[]> soft2 = new SoftReference<>(new byte[2000 * MB]);// 再次申請2000MB的內存
printlnMemory("use after");
System.out.println("gc soft:" + soft.get());
}
運行情況
Init:239M(free)/245M(total)
Use 2000MB:239M(free)/2246M(total)//總內存變大了
GC after:243M(free)/2246M(total) //內存足夠沒有回收
gc soft:[B@2db0f6b2
use after:471M(free)/2474M(total)//內存不夠,自動回收
gc soft:null
- 弱引用
WeakReference<Object> soft = new WeakReference<>(new Object());
弱引用用來描述非必需對象的,當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象。也可以用來構建敏感數據的緩存,如用于生命周期更短的,對內存更敏感的場景中,比如占用內存很大的Map,java提供了WeakHashMap。可以看下下面代碼和運行情況
private static void weakTest() {
printlnMemory("Init");
WeakReference<byte[]> weak= new WeakReference<>(new byte[10 * MB]);
printlnMemory("Use 10MB");
System.gc();
printlnMemory("GC after");
System.out.println("gc weak:" + weak.get());
}
運行情況
Init:239M(free)/245M(total)
Use 10MB:229M(free)/245M(total)
GC after:243M(free)/245M(total)//不管內存是否充足,都進行回收
gc soft:null //weak.get無法再回去對象
- 虛引用
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
PhantomReference<Object> phantom = new PhantomReference<Object>(new Object(), queue);
若一個對象擁有虛引用,則在任何時候都可能被回收。虛引用必須和引用隊列聯合使用,當所引用的對象被回收,虛引用便被加入到引用隊列,主要用來追蹤垃圾回收過程。
private static void phantomTest() {
printlnMemory("Init");
byte[] bytes = new byte[5 * MB];
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
PhantomReference<Object> phantom = new PhantomReference<Object>(bytes, queue);
printlnMemory("Use 5MB");
System.out.println("phantom : " + phantom);
System.out.println("phantom.get() : " + phantom.get());
System.out.println("queue.poll() : " + queue.poll());
//斷開強引用
bytes = null;
System.gc();
printlnMemory("GC after bytes");
System.out.println("phantom : " + phantom);
System.out.println("phantom.get() : " + phantom.get());
System.out.println("queue.poll() : " + queue.poll());
//斷開虛引用
phantom = null;
System.gc();
printlnMemory("GC after phantom");
System.out.println("phantom : " + phantom);
System.out.println("queue.poll() : " + queue.poll());
}
運行情況
Init:239M(free)/245M(total)
Use 5MB:234M(free)/245M(total)
phantom : java.lang.ref.PhantomReference@2db0f6b2
phantom.get() : null
queue.poll() : null
GC after bytes:238M(free)/245M(total)
phantom : java.lang.ref.PhantomReference@2db0f6b2
phantom.get() : null
queue.poll() : java.lang.ref.PhantomReference@2db0f6b2
GC after phantom:243M(free)/245M(total)
phantom : null
queue.poll() : null
- ReferenceQueue
顧名思義存放引用的隊列,保存的是Reference對象,其作用在于Reference對象所引用的對象被GC回收時,該Reference對象將會被加入引用隊列中的隊列末尾。
常用的方法:
- poll():從隊列中取出一個元素,隊列為空則返回null
- remove():從隊列中出對一個元素,若沒有則阻塞至有可出隊元素
- remove(long timeout):從隊列中出對一個元素,若沒有則阻塞至有可出對元素或阻塞至超過timeout毫秒;
可以看下下面代碼
byte[] bytes = new byte[5 * MB];
ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
PhantomReference<Object> phantom = new PhantomReference<Object>(bytes, queue);
這段代碼中,對于byte對象有兩種引用類型,一是bytes 的強引用,二是phantom 的虛引用。當bytes 被回收時,phantom 所引用的對象將會被放到queue 的隊列末尾。利用ReferenceQueue可以清除失去了虛引用對象的引用。