面試官考察Java參照會問到強參照、弱參照、軟參照、虛參照,具體有什麼區別?本篇單獨來詳解 @mikechen
從JDK 1.2版本開始,物件的參照被劃分為4種級別,從而使程式能更加靈活地控制物件的生命週期,這4種級別由高到低依次為:強參照、軟參照、弱參照和虛參照。
強參照是最普遍的參照,一般把一個物件賦給一個參照變數,這個參照變數就是強參照。
比如:
// 強參照 MikeChen mikechen=new MikeChen();
在一個方法的內部有一個強參照,這個參照儲存在Java棧中,而真正的參照內容(MikeChen)儲存在Java堆中。
如果一個物件具有強參照,垃圾回收器不會回收該物件,當記憶體空間不足時,JVM 寧願丟擲 OutOfMemoryError異常。
如果強參照物件不使用時,需要弱化從而使GC能夠回收,如下:
//幫助垃圾收集器回收此物件 mikechen=null;
顯式地設定mikechen物件為null,或讓其超出物件的生命週期範圍,則GC認為該物件不存在參照,這時就可以回收這個物件,具體什麼時候收集這要取決於GC演演算法。
舉例:
package com.mikechen.java.refenence; /** * 強參照舉例 * * @author mikechen */ public class StrongRefenenceDemo { public static void main(String[] args) { Object o1 = new Object(); Object o2 = o1; o1 = null; System.gc(); System.out.println(o1); //null System.out.println(o2); //java.lang.Object@2503dbd3 } }
StrongRefenenceDemo 中儘管 o1已經被回收,但是 o2 強參照 o1,一直存在,所以不會被GC回收。
軟參照是一種相對強參照弱化了一些的參照,需要用java.lang.ref.SoftReference 類來實現。
比如:
String str=new String("abc"); // 強參照 SoftReference<String> softRef=new SoftReference<String>(str); // 軟參照
如果一個物件只具有軟參照,則記憶體空間足夠,垃圾回收器就不會回收它,如果記憶體空間不足了,就會回收這些物件的記憶體。
先通過一個例子來了解一下軟參照:
/** * 弱參照舉例 * * @author mikechen */ Object obj = new Object(); SoftReference softRef = new SoftReference<Object>(obj);//刪除強參照 obj = null;//呼叫gc // 物件依然存在 System.gc();System.out.println("gc之後的值:" + softRef.get());
軟參照可以和一個參照佇列(ReferenceQueue)聯合使用,如果軟參照所參照物件被垃圾回收,Java虛擬機器器就會把這個軟參照加入到與之關聯的參照佇列中。
ReferenceQueue<Object> queue = new ReferenceQueue<>(); Object obj = new Object(); SoftReference softRef = new SoftReference<Object>(obj,queue);//刪除強參照 obj = null;//呼叫gc System.gc(); System.out.println("gc之後的值: " + softRef.get()); // 物件依然存在 //申請較大記憶體使記憶體空間使用率達到閾值,強迫gc byte[] bytes = new byte[100 * 1024 * 1024];//如果obj被回收,則軟參照會進入參照佇列 Reference<?> reference = queue.remove();if (reference != null){ System.out.println("物件已被回收: "+ reference.get()); // 物件為null }
軟參照通常用在對記憶體敏感的程式中,比如快取記憶體就有用到軟參照,記憶體夠用的時候就保留,不夠用就回收。
我們看下 Mybatis 快取類 SoftCache 用到的軟參照:
public Object getObject(Object key) { Object result = null; SoftReference<Object> softReference = (SoftReference)this.delegate.getObject(key); if (softReference != null) { result = softReference.get(); if (result == null) { this.delegate.removeObject(key); } else { synchronized(this.hardLinksToAvoidGarbageCollection) { this.hardLinksToAvoidGarbageCollection.addFirst(result); if (this.hardLinksToAvoidGarbageCollection.size() > this.numberOfHardLinks) { this.hardLinksToAvoidGarbageCollection.removeLast(); } } } } return result;}
注意:軟參照物件是在jvm記憶體不夠的時候才會被回收,我們呼叫System.gc()方法只是起通知作用,JVM什麼時候掃描回收物件是JVM自己的狀態決定的,就算掃描到軟參照物件也不一定會回收它,只有記憶體不夠的時候才會回收。
弱參照的使用和軟參照類似,只是關鍵字變成了 WeakReference:
MikeChen mikechen = new MikeChen(); WeakReference<MikeChen> wr = new WeakReference<MikeChen>(mikechen );
弱參照的特點是不管記憶體是否足夠,只要發生 GC,都會被回收。
舉例說明:
public class WeakHashMapDemo { public static void main(String[] args) throws InterruptedException { myHashMap(); myWeakHashMap(); } public static void myHashMap() { HashMap<String, String> map = new HashMap<String, String>(); String key = new String("k1"); String value = "v1"; map.put(key, value); System.out.println(map); key = null; System.gc(); System.out.println(map); } public static void myWeakHashMap() throws InterruptedException { WeakHashMap<String, String> map = new WeakHashMap<String, String>(); //String key = "weak"; // 剛開始寫成了上邊的程式碼 //思考一下,寫成上邊那樣會怎麼樣? 那可不是參照了 String key = new String("weak"); String value = "map"; map.put(key, value); System.out.println(map); //去掉強參照 key = null; System.gc(); Thread.sleep(1000); System.out.println(map); }}
弱參照的應用
WeakHashMap
public class WeakHashMapDemo { public static void main(String[] args) throws InterruptedException { myHashMap(); myWeakHashMap(); } public static void myHashMap() { HashMap<String, String> map = new HashMap<String, String>(); String key = new String("k1"); String value = "v1"; map.put(key, value); System.out.println(map); key = null; System.gc(); System.out.println(map); } public static void myWeakHashMap() throws InterruptedException { WeakHashMap<String, String> map = new WeakHashMap<String, String>(); //String key = "weak"; // 剛開始寫成了上邊的程式碼 //思考一下,寫成上邊那樣會怎麼樣? 那可不是參照了 String key = new String("weak"); String value = "map"; map.put(key, value); System.out.println(map); //去掉強參照 key = null; System.gc(); Thread.sleep(1000); System.out.println(map); }}
當key只有弱參照時,GC發現後會自動清理鍵和值,作為簡單的快取表解決方案。
ThreadLocal
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } //......}
ThreadLocal.ThreadLocalMap.Entry 繼承了弱參照,key為當前執行緒範例,和WeakHashMap基本相同。
虛參照」顧名思義,就是形同虛設,與其他幾種參照都不同,虛參照並不會決定物件的生命週期。如果一個物件僅持有虛參照,那麼它就和沒有任何參照一樣,在任何時候都可能被垃圾回收器回收。
虛參照也稱為「幽靈參照」或者「幻影參照」,它是最弱的一種參照關係。
虛參照需要java.lang.ref.PhantomReference 來實現:
A a = new A(); ReferenceQueue<A> rq = new ReferenceQueue<A>(); PhantomReference<A> prA = new PhantomReference<A>(a, rq);
虛參照主要用來跟蹤物件被垃圾回收器回收的活動。
虛參照與軟參照和弱參照的一個區別在於:虛參照必須和參照佇列 (ReferenceQueue)聯合使用,當垃圾回收器準備回收一個物件時,如果發現它還有虛參照,就會在回收物件的記憶體之前,把這個虛參照加入到與之 關聯的參照佇列中。
java4種參照的級別由高到低依次為:強參照 > 軟參照 > 弱參照 > 虛參照。
以上
陳睿|mikechen,10年+大廠架構經驗,《BAT架構技術500期》系列文章作者,分享十餘年BAT架構經驗以及面試心得!
閱讀mikechen的網際網路架構更多技術文章合集
Java並行|JVM|MySQL|Spring|Redis|分散式|高並行|架構師