Java 與 Android 運行時提供四種引用層級,決定物件在 GC 掃描時的存活優先權。理解其語義差異、回收時機與典型應用場景,對防止記憶體洩漏、實現快取、管理資源生命週期至關重要。
| 引用類型 | 類別 | 回收時機 | 典型用途 | get() 可能為 null |
|---|---|---|---|---|
| 強引用 | - | 永不主動回收(OOM 前) | 一般物件持有 | 否 |
| 軟引用 | SoftReference | 記憶體不足時 | 記憶體敏感快取(圖片、網路資料) | 是 |
| 弱引用 | WeakReference | 下次 GC 時 | 監聽器、回調、ThreadLocalKey、ClassLoader 快取 | 是 |
| 虛引用 | PhantomReference | 被回收後 | 精確追蹤回收時機、直接記憶體釋放、清理動作 | 永遠為 null |
Object obj = new Object(); // 強引用
// obj = null 且無其他強引用指向 → 可被 GC 回收
最常見形式。強引用存在期間,GC 絕不 回收該物件,寧願拋 OutOfMemoryError。
SoftReference<Bitmap> softRef = new SoftReference<>(bitmap);
// 記憶體充足:softRef.get() 返回物件
// 記憶體告急:GC 會清理軟引用指向的物件,get() 返回 null
特性:
ReferenceQueue 監聽真正被回收的瞬間。WeakReference<Listener> weakListener = new WeakReference<>(listener);
// 無強引用時,下一次 GC(Young/Full)即會回收,get() 返回 null
核心場景:
ThreadLocalMap 的 Key 即為 WeakReference<ThreadLocal>。public class MainActivity extends AppCompatActivity {
// 靜態內部類 + 弱引用持有外部 Activity
private static class SafeHandler extends Handler {
private final WeakReference<MainActivity> ref;
SafeHandler(MainActivity act) { ref = new WeakReference<>(act); }
@Override public void handleMessage(Message msg) {
MainActivity act = ref.get();
if (act == null) return; // Activity 已銷毀,放棄處理
// 正常更新 UI
act.updateUI(msg);
}
}
private final SafeHandler handler = new SafeHandler(this);
}
非靜態內部類隱含持有外部類引用,導致 Activity 無法回收。靜態內部類 + WeakReference 打破循環引用。
ReferenceQueue<Object> queue = new ReferenceQueue<>();
PhantomReference<Object> phantom = new PhantomReference<>(obj, queue);
// phantom.get() 永遠返回 null
// 當 obj 被 GC 回收後,phantom 會被 enqueue 進 queue
// 專用線程輪詢 queue.poll() 執行清理邏輯(如 DirectBuffer 釋放)
唯一構造器:PhantomReference(T referent, ReferenceQueue<? super T> q) —— 必須 綁定 ReferenceQueue。
核心用途:
DirectByteBuffer 釋放堆外記憶體(Cleaner 內部即用虛引用)。finalize(),避免其不可預測的執行時機與效能問題。ReferenceQueue<MyObject> rq = new ReferenceQueue<>();
WeakReference<MyObject> wr = new WeakReference<>(obj, rq);
// 當 obj 真正被 GC 回收後(非僅進入最終化隊列),wr 會被放入 rq
// 專用線程處理:
new Thread(() -> {
while (true) {
Reference<?> ref = rq.remove(); // 阻塞等待
// 執行清理:關閉檔案、釋放連線、移除快取條目等
}
}).start();
關鍵區別:
get() == null:物件已進入最終化/回收流程,但尚未確定回收完成。ReferenceQueue:物件確已回收完成,可安全執行後續清理。java.lang.ref.Cleaner 作為 PhantomReference 的高層封裝,優先考慮使用。| 需求 | 推薦引用 | 理由 |
|---|---|---|
| 普通物件持有 | 強引用 | 預設行為,簡單直接 |
| 記憶體快取,希望盡量保留 | 軟引用 | 記憶體緊張才清理 |
| 監聽器/回調,不阻礙目標回收 | 弱引用 | 下次 GC 即回收 |
| 需精確感知回收瞬間執行清理 | 虛引用 + ReferenceQueue | 回收後通知,get() 為 null |
| ThreadLocal Key | 弱引用 | JDK 內建機制 |