Java中爲了讓程序員能夠本身控制對象生命週期,提供了四種引用方式,都繼承自java.lang.ref.Reference
類,它們分別是:強引用、軟引用、弱引用、虛引用。html
在Java中像Object obj = new Object();
這種形式產生的引用都是強引用。強引用能夠直接訪問引用對象,並且強引用只在存在,那麼引用對象就不會被JVM垃圾回收,即便JVM拋出OOM異常的時候,也不回收強引用引用的對象,因此不正確的使用方式可能會形成內存泄漏。 表示強引用的類java.lang.ref.FinalReference
是包級範圍訪問的,因此外部不能直接使用,而其子類java.lang.ref.Finalizer
一樣是包級範圍訪問,因此通常程序員不會使用它們。 下面是一段測試代碼,測試強引用對象是否會被GC掉(在VM中增長-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xmx5m參數,用於打印GC日誌和設置JVM最大內存)。java
// 經過new的方式生成一個強引用 Object obj = new Object(); // java.lang.Object@5702b3b1 System.out.println(obj); // 執行GC System.gc(); // 執行GC後,強引用不會被回收 // java.lang.Object@5702b3b1 System.out.println(obj); // 分配一塊大內存(5M),此時JVM內存耗盡,實際會拋出OOM錯誤,這裏捕獲掉,觀察軟引用引用的對象是否被回收 // java.lang.OutOfMemoryError: Java heap space try { byte[] buf = new byte[5 * 1024 * 1024]; } catch (Throwable t) { System.out.println(t.getMessage()); } // 即便內存溢出,也不回收強引用對象 // java.lang.Object@5702b3b1 System.out.println(obj);
軟引用用於描述有用但不是很重要的對象,這種對象在JVM內存充足時不會被GC掉,但在內存緊張甚至OOM時,會被GC。 軟引用可用來實現內存敏感的高速緩存。軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。程序員
// 將這個對象賦值給軟引用 SoftReference<Object> sr = new SoftReference<>(new Object()); // soft_ref_1 = java.lang.Object@155ec9f4 System.out.println("soft_ref_1 = " + sr.get()); // 此時對象只有一個軟件引用了,執行GC System.gc(); // 說明在內存充足的狀況下,GC不會回收軟引用對象 // soft_ref_2 = java.lang.Object@5a6d6fc5 System.out.println("soft_ref_2 = " + sr.get()); // 分配一塊大內存(5M),此時JVM內存耗盡,實際會拋出OOM錯誤,這裏捕獲掉,觀察軟引用引用的對象是否被回收 // java.lang.OutOfMemoryError: Java heap space try { byte[] buf = new byte[5 * 1024 * 1024]; } catch (Throwable t) { // t.printStackTrace(); } // 說明JVM內存耗盡時會回收軟引用對象 // soft_ref_3 = null System.out.println("soft_ref_3 = " + sr.get());
弱引用是一種比軟引用強度更弱的引用,功能與軟引用相似,但區別在於,只要JVM發生GC,弱引用引用的對象就會被回收。面試
ReferenceQueue<Object> queue = new ReferenceQueue<>(); // 構造一個弱引用 // WeakReference<Object> ref = new WeakReference<>(new Object()); WeakReference<Object> ref = new WeakReference<>(new Object(), queue); // weak_ref_1 = java.lang.Object@5702b3b1 System.out.println("weak_ref_1 = " + ref.get()); // null System.out.println(queue.remove(200L)); // 此時對象只有一個軟件引用了,執行GC System.gc(); // 即便在內存充足的狀況下,只要發生GC就會回收弱引用對象 // weak_ref_2 = null System.out.println("weak_ref_2 = " + ref.get()); // 當垃圾對象被回收後,弱引用被加入到引用隊列中 // java.lang.ref.WeakReference@22927a81 System.out.println(queue.remove(200L)); System.gc(); // null System.out.println(queue.remove(200L));
虛引用和前面的軟引用、弱引用不一樣,它並不影響對象的生命週期,這種引用引用的對象隨時均可能被回收,並且它的get方法永遠返回null。緩存
public class PhantomReference<T> extends Reference<T> { public T get() { return null; } public PhantomReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
虛引用必須和引用隊列一塊兒使用,用於跟蹤垃圾回收過程。ide
public class PhantomReferenceTest { static class MyType { @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("--finalize--"); } @Override public String toString() { return "--string--"; } } @Test public void test() throws InterruptedException { // 構造虛引用必須傳入引用隊列 ReferenceQueue<MyType> queue = new ReferenceQueue<>(); PhantomReference<MyType> ref = new PhantomReference<>(new MyType(), queue); // 這種引用跟沒有引用幾乎沒有分別,任什麼時候候經過get方法取得的都是空值 // phantom_ref_1 = null System.out.println("phantom_ref_1 = " + ref.get()); // 執行GC,第一次:經過觀察輸出日誌發現,JVM找到了垃圾對象,並調用finalize方法回收內存,但沒有當即加入回收隊列 System.gc(); System.out.println("GC - 1"); // --finalize-- // null System.out.println(queue.remove(200L)); // 執行GC,第二次,經過觀察輸出日誌發現,第二次GC後JVM才真正清除垃圾對象,並將其加入引用隊列(因此才能從隊列中彈出值) System.gc(); System.out.println("GC - 2"); // java.lang.ref.PhantomReference@32a1bec0 System.out.println(queue.remove(200L)); // 執行GC,第三次,後面的GC與該對象已經無關 System.gc(); System.out.println("GC - 3"); // null System.out.println(queue.remove(200L)); } }
在一次面試中面試官問到了這個問題,當時我只知道強引用和弱引用(其實是軟引用),這種基礎的問題答不上來還真是挺尷尬的。測試
參考資料:spa