Java 中所謂的引用,看似是指針的問題,實則體現的是JVM對記憶體的管理思想。 -- 魯迅 ...
介紹
在JAVA中提供了四種引用類型:強引用、軟引用、軟引用和虛引用。
在四種引用類型中,只有強引用FinalReference類型變數是包內可見的,其他三種引用類型均為public,可以在程式中直接使用。
強引用
強引用是使用最普遍的引用。如果一個對象具有強引用,那麼垃圾回收器絕不會回收它。
例如:
StringBuilder sb = new StringBuilder("test");
變數str指向StringBuffer實例所在的堆空間,通過str可以操作該對象。
如下:
- 強引用可以直接訪問目標對象
- 只要有引用變數存在,垃圾回收器永遠不會回收。JVM即使拋出OOM異常,也不會回收強引用所指向的對象。
- 強引用可能導致記憶體泄漏問
軟引用
軟引用是除了強引用外,最強的引用類型。一個持有軟引用的對象,只有在記憶體不足時,gc才會回收它。
可以通過java.lang.ref.SoftReference使用軟引用。
SoftReference
的特點是:
一旦SoftReference保存了一個Java對象的軟引用後
- 在垃圾線程對這個Java對象回收前,SoftReference類所提供的get()方法返回Java對象的強引用。
- 一旦垃圾線程回收該Java對象之後,get()方法將返回null。
如下:
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
SoftReference<Object> sf = new SoftReference<Object>(obj, queue); // 只能這麼創建
obj = null;
System.out.println("引用值:" + sf.get());
System.out.println("被標記:" + sf.isEnqueued());
System.out.println("被回收:" + queue.poll());
System.gc();
System.out.println("引用值:" + sf.get() + "(gc後)"); // 有gc,不一定為null。記憶體不足時,為null
System.out.println("被標記:" + sf.isEnqueued() + "(gc後)");
System.out.println("被回收:" + queue.poll() + "(gc後)");
- sf是對obj的一個軟引用,通過sf.get()方法可以取到
這個對象
- 當
這個對象
被標記為需要回收的對象時,則返回null
(上面例子不為null,說明對象未被gc標記為垃圾)
一個持有軟引用的對象,不會被JVM很快回收,JVM會根據當前堆的使用情況來判斷何時回收。當堆使用率臨近閾值時,才會去回收軟引用的對象。
-
因此,軟引用可以用於實現對記憶體敏感的高速緩存。
- 在記憶體足夠的情況下直接通過軟引用取值,無需從繁忙的真實來源查詢數據,提升速度;
- 當記憶體不足時,自動刪除這部分緩存數據,從真正的來源查詢這些數據。
-
使用軟引用能防止記憶體泄露,增強程式的健壯性。
記憶體泄漏:既存在一部分記憶體一直處於空閑狀態。
-
軟引用可以和一個引用隊列(ReferenceQueue)聯合使用
ReferenceQueue中保存已經失去了它所軟引用的對象的Reference對象。
(參考“弱引用”)利用ReferenceQueue的poll()方法,可以檢查哪個SoftReference所軟引用的對象已經被回收,於是可以把這些失去所軟引用的對象的SoftReference對象清除掉。
弱引用
弱引用是一種比軟引用較弱的引用類型。一個被弱引用的對象,不管系統堆空間是否足夠,gc都會將對象進行回收。
在java中,可以用java.lang.ref.WeakReference實例來保存對一個Java對象的弱引用。
Object obj = new Object();
ReferenceQueue<Object> queue = new ReferenceQueue<>();
WeakReference<Object> sf = new WeakReference<Object>(obj, queue); // 只能這麼創建
obj = null;
System.out.println("引用值:" + sf.get());
System.out.println("被標記:" + sf.isEnqueued());
System.out.println("被回收:" + queue.poll());
System.gc();
System.out.println("引用值:" + sf.get() + "(gc後)"); // 只要有gc,就為null
System.out.println("被標記:" + sf.isEnqueued() + "(gc後)");
System.out.println("被回收:" + queue.poll() + "(gc後)");
弱引用的應用:WeakHashMap
Object key = new Object();
// WeakHashMap 的弱引用對象是key,而value是強引用存儲在WeakHashMap內部
WeakHashMap<Object, Object> map = new WeakHashMap<>();
map.put(key, "haha");
System.out.println("obj:" + map.get(key));
System.out.println("size:" + map.size());
System.gc();
System.out.println("obj:" + map.get(key) + "(gc後)");
System.out.println("size:" + map.size());
// 清除強引用
key = null;
// 調用gc清除弱引用
System.gc();
System.out.println("obj:" + map.get(key) + "(gc後,obj=null後)");
System.out.println("size:" + map.size());
最後提醒一句:WeakHashMap 不是線程安全的,要在併發場景下使用,記得使用 Collections.synchronizedMap 包一層
虛引用
虛引用是所有類型中最弱的一個。一個持有虛引用的對象和沒有引用幾乎是一樣的,隨時可能被垃圾回收器回收。當試圖通過虛引用的get()方法取得強引用時,總是會失敗。
phantom 幽靈
Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue<>();
PhantomReference sf = new PhantomReference(obj, queue); // 只能這麼創建
obj = null;
System.out.println("引用值:" + sf.get());
System.out.println("被標記:" + sf.isEnqueued());
System.out.println("被回收:" + queue.poll());
System.gc();
System.out.println("引用值:" + sf.get() + "(gc後)");
System.out.println("被標記:" + sf.isEnqueued() + "(gc後)");
System.out.println("被回收:" + queue.poll() + "(gc後)");
虛引用必須和引用隊列一起使用,它的作用在於檢測對象是否已經從記憶體中刪除,跟蹤垃圾回收過程。
當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在垃圾回收後,銷毀這個對象,將這個虛引用加入引用隊列。程式可以通過判斷引用隊列中是否已經加入了虛引用,來瞭解被引用的對象是否將要被垃圾回收。
參考: