JDK 1.2 引入四級引用機制,賦予開發者對對象生命週期的精細控制權。本文系統梳理強/軟/弱/虛引用的語義差異、可達性判斷規則,並通過「軟引用構建內存敏感緩存」「弱引用修復 Map 洩漏」兩大實戰案例,展示如何在高併發、大數據場景下優雅管理內存。
| 引用類型 | GC 回收時機 | 典型用途 | 需配合 ReferenceQueue |
|---|---|---|---|
| 強引用 | 永不主動回收(寧願 OOM) | 常規對象持有 | 否 |
| 軟引用 | 內存不足前回收 | 高速緩存、圖片池 | 可選 |
| 弱引用 | 下次 GC 必回收 | 元數據映射、監聽器 | 可選 |
| 虛引用 | 隨時可能回收 | 堆外內存釋放、對象消亡監控 | 強制 |
例:對象同時被強引用與弱引用指向 → 強可達(不回收)。
場景:員工信息查詢系統,頻繁重複查詢同一員工,DB 壓力大。
class EmployeeRef extends SoftReference<Employee> {
private String _key; // 員工 ID,用於從 Map 中快速移除
EmployeeRef(Employee em, ReferenceQueue<Employee> q) {
super(em, q);
_key = em.getID();
}
}
public class EmployeeCache {
private Hashtable<String, EmployeeRef> employeeRefs = new Hashtable<>();
private ReferenceQueue<Employee> q = new ReferenceQueue<>();
public Employee getEmployee(String id) {
EmployeeRef ref = employeeRefs.get(id);
Employee em = (ref != null) ? ref.get() : null; // 獲取強引用
if (em == null) { // 緩存未命中或已被 GC
em = new Employee(id); // 昂貴的 DB 查詢
cacheEmployee(em);
}
return em;
}
private void cacheEmployee(Employee em) {
cleanCache(); // 清理已失效的 Ref
employeeRefs.put(em.getID(), new EmployeeRef(em, q));
}
private void cleanCache() {
EmployeeRef ref;
while ((ref = (EmployeeRef) q.poll()) != null) {
employeeRefs.remove(ref._key); // 從 Map 移除殭屍 Ref
}
}
}
優勢: 內存充裕時緩存命中加速;內存緊張時 GC 自動回收軟可達對象,緩存自動縮容,零配置實現「內存敏感」。
痛點:Map<Socket, User> 綁定連接元數據,Socket 關閉後 Map 未移除 → Key/Value 永不被 GC。
// 錯誤:強引用 Key,生命週期無法自動結束
Map<Socket, User> map = new HashMap<>();
// 正確:WeakHashMap 持有 Key 的弱引用,Key 無強引用時自動移除條目
Map<Socket, User> map = new WeakHashMap<>();
原理: WeakHashMap 內部使用 WeakReference<K> + ReferenceQueue,每次操作調用 expungeStaleEntries() 清理已回收 Key 對應的 Entry。
關聯類可標注 <<soft>>、<<weak>>、<<phantom>> 構造型,明確表達引用語義,指導代碼實現。