引用類型層級關係
強引用 > 軟引用 > 弱引用 > 虛引用
(生命週期最長) (生命週期最短)
| 引用類型 | 建立方式 | GC 回收時機 | 典型用途 | 獲取對象 |
| 強引用 | Object obj = new Object() | **永不回收** (寧願 OOM) | 普通物件持有 | 直接 obj |
| 軟引用 | SoftReference<T> ref = new SoftReference<>(obj) | **記憶體不足時** 回收 | **記憶體敏感快取** (圖片、網路資料) | ref.get() 可能為 null |
| 弱引用 | WeakReference<T> ref = new WeakReference<>(obj) | **下次 GC 時** 回收 | **非強制性映射** (ThreadLocal、ClassLoader、監聽器) | ref.get() 可能為 null |
| 虛引用 | PhantomReference<T> ref = new PhantomReference<>(obj, queue) | **隨時可能** 回收 | **精確追蹤回收**、**堆外記憶體釋放** (DirectByteBuffer) | ref.get() 永遠返回 null |
代碼演示
public class ReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 強引用
Object strong = new Object();
System.out.println("強引用: " + strong); // 永不為 null
// 2. 軟引用
SoftReference<Object> soft = new SoftReference<>(new Object());
System.gc(); // 記憶體充足不回收
System.out.println("GC後軟引用: " + soft.get()); // 仍存在
// 模擬 OOM (需 -Xmx10m)
// List<byte[]> list = new ArrayList<>();
// while (true) list.add(new byte[1024*1024]); // 軟引用會被清理
// 3. 弱引用
WeakReference<Object> weak = new WeakReference<>(new Object());
System.gc(); // 下次 GC 回收
System.out.println("GC後弱引用: " + weak.get()); // 可能為 null
// 4. 虛引用 + ReferenceQueue
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), queue);
System.out.println("虛引用 get(): " + phantom.get()); // 永遠 null
// 當對象被回收,phantom 會被放入 queue
new Thread(() -> {
try {
Reference<?> ref = queue.remove(); // 阻塞等待
System.out.println("對象已被回收,執行清理邏輯: " + ref);
// 釋放堆外記憶體、關閉資源等
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
System.gc();
Thread.sleep(1000);
}
}
實戰應用場景
1. 軟引用:圖片快取 (LruCache 內部原理)
public class ImageCache {
private final Map<String, SoftReference<Bitmap>> cache = new HashMap<>();
public void put(String key, Bitmap bitmap) {
cache.put(key, new SoftReference<>(bitmap));
}
public Bitmap get(String key) {
SoftReference<Bitmap> ref = cache.get(key);
return ref != null ? ref.get() : null; // 可能已被 GC 回收
}
}
// 配合 LruCache 使用:記憶體緊張時自動淘汰軟引用持有的 Bitmap
2. 弱引用:ThreadLocal 實現原理
// ThreadLocalMap Entry 繼承 WeakReference>
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
// 當 ThreadLocal 無強引用時,Entry 自動變弱可達,GC 時清理 value,防止記憶體洩漏
3. 弱引用:監聽器模式避免洩漏
public class EventBus {
private final List> listeners = new ArrayList<>();
public void register(Listener l) {
listeners.add(new WeakReference<>(l));
}
public void post(Event e) {
listeners.removeIf(ref -> ref.get() == null); // 清理已回收
for (WeakReference ref : listeners) {
Listener l = ref.get();
if (l != null) l.onEvent(e);
}
}
}
// 監聽者無需顯式 unregister,GC 自動清理
4. 虛引用:DirectByteBuffer 堆外記憶體釋放
// Java 內部 Cleaner 機制 (Java 9+)
public class DirectBufferCleaner {
public static void main(String[] args) {
ByteBuffer buf = ByteBuffer.allocateDirect(1024 * 1024); // 1MB 堆外
// 內部建立 PhantomReference + ReferenceQueue
// GC 時 Cleaner 執行 freeMemory(address)
}
}
// 傳統 finalize() 已棄用,PhantomReference + Cleaner 是標準替代
ReferenceQueue 參考隊列
- 軟/弱/虛引用建立時可選擇性傳入
ReferenceQueue
- 當引用對象被 GC 判定為可回收,**引用對象本身**會被放入隊列
- 專用執行緒輪詢
queue.remove() 或 poll() 執行清理邏輯
- 虛引用**必須**配合隊列使用,否則無意義
總結
- 強引用:預設、最強、寧願 OOM 不回收
- 軟引用:快取首選、記憶體不足才回收、配合 LRU
- 弱引用:輔助映射、ThreadLocal、監聽器、GC 即回收
- 虛引用:回收追蹤、堆外記憶體釋放、必配 ReferenceQueue
- **獲取對象統一用
ref.get(),需檢查 null** (虛引用永遠 null)
- 理解四者 GC 行為差異,能設計出**記憶體友好、無洩漏**的 Java 系統