強參考 vs 弱參考
| 特性 | 強參考 (預設) | 弱參考 (WeakReference) |
| GC 回收條件 | 無強參考指向時 | 無強參考指向時 (弱參考不計入) |
| 記憶體壓力下 | 保留至必要時 | 優先回收,釋放記憶體 |
| 存取目標物件 | 直接存取 | 透過 Target 屬性取得強參考 |
| 適用場景 | 一般物件存活控制 | 快取、大型物件、觀察者模式、避免循環參考 |
基本用法
// 1. 建立目標物件與弱參考
var heavyObject = new LargeCacheObject(); // 佔用大量記憶體
var weakRef = new WeakReference(heavyObject);
// 2. 釋放強參考
heavyObject = null; // 此時只有弱參考指向目標
// 3. 需要時嘗試取回強參考
object target = weakRef.Target;
if (target != null) {
// 成功取得強參考,目標物件尚未被回收
var cache = (LargeCacheObject)target;
cache.DoWork();
} else {
// 目標已被 GC 回收,需重建
heavyObject = new LargeCacheObject();
weakRef.Target = heavyObject; // 更新弱參考指向新物件
}
兩種弱參考模式:Short vs Long
// 建構函式:public WeakReference(object target, bool trackResurrection)
/* Short Weak Reference (預設,trackResurrection = false) */
var shortRef = new WeakReference(obj, false);
// 行為:物件被標記為最終化 (Finalize) 時,Target 立即變 null
// 特點:不追蹤物件復活,效能較好
/* Long Weak Reference (trackResurrection = true) */
var longRef = new WeakReference(obj, true);
// 行為:物件 Finalize 執行完成前,Target 仍可存取
// 風險:Finalize 可能已釋放非受控資源,存取成員需極度小心
// 用途:極少見,除非需在 Finalize 期間觀察物件狀態
WeakReference 內部機制
- GC 維護兩張**弱參考表** (Short/Long Weak Reference Table),非 GC Roots 一部分。
- 建立
WeakReference 時,在對應表中登記目標物件位址。
- GC 掃描時:若物件無強參考,檢查弱參考表,將對應項目設為
null (Short) 或保留至 Finalize 完成 (Long)。
Target 屬性 getter 實際上呼叫 GCHandle.GetTarget() 從表中查詢。
進階應用場景
1. 記憶體敏感型快取
public class WeakCache where TValue : class {
private readonly Dictionary> _cache = new();
private readonly object _lock = new();
public bool TryGet(TKey key, out TValue value) {
lock (_lock) {
if (_cache.TryGetValue(key, out var weakRef) && weakRef.TryGetTarget(out value)) {
return true;
}
value = null;
return false;
}
}
public void Set(TKey key, TValue value) {
lock (_lock) {
_cache[key] = new WeakReference(value);
}
}
// 定期清理已失效項目
public void Cleanup() {
lock (_lock) {
var deadKeys = _cache.Where(kvp => !kvp.Value.TryGetTarget(out _)).Select(kvp => kvp.Key).ToList();
foreach (var k in deadKeys) _cache.Remove(k);
}
}
}
// .NET 4.5+ 強型別 WeakReference 避免轉型
var cache = new WeakCache();
2. 事件訂閱者弱參考 (避免記憶體洩漏)
public class EventPublisher {
private readonly List>> _subscribers = new();
public void Subscribe(EventHandler handler) {
_subscribers.Add(new WeakReference>(handler));
}
public void Publish(object sender, EventArgs e) {
// 清理死弱參考並通知存活者
for (int i = _subscribers.Count - 1; i >= 0; i--) {
if (_subscribers[i].TryGetTarget(out var handler)) {
handler(sender, e);
} else {
_subscribers.RemoveAt(i); // 移除已回收的訂閱者
}
}
}
}
3. ASP.NET 頁面實例快取 (原始碼範例)
// ASP.NET 內部使用 WeakReference 追蹤頁面實例
public class AspPage : Page {
private static ArrayList __ENCList = new ArrayList();
public AspPage() {
Load += Page_Load;
lock (__ENCList) {
__ENCList.Add(new WeakReference(this)); // 頁面實例不阻礙 GC
}
}
}
WeakReference<T> (.NET 4.5+) - 強型別版本
// 避免 (T)weakRef.Target 轉型,直接強型別存取
var weak = new WeakReference(new BigObject());
if (weak.TryGetTarget(out BigObject obj)) {
obj.Process(); // 編譯期型別安全
}
// 更新目標
weak.SetTarget(new BigObject());
效能考量與注意事項
- 額外開銷:每個
WeakReference 佔用 ~24 bytes (含 GCHandle),大量使用時需權衡。
- 非確定性回收:無法預測何時被回收,
Target 可能在檢查非 null 後立即失效 (極罕見),建議模式:if (weak.TryGetTarget(out var obj)) { ... } 原子操作。
- 不可序列化:
WeakReference 不支援序列化,跨 AppDomain/進程傳遞需自行處理。
- Finalize 干擾:Long Weak Reference 可能存取已部分銷毀的物件,除非極度確定,請使用 Short (預設)。
何時使用 WeakReference?決策清單
✓ 物件建立成本高、佔用記憶體大 (大圖片、報表、快取資料)
✓ 物件非持續需要,可重建 (可捨棄式快取)
✓ 發布-訂閱模式,發布者不應阻止訂閱者被回收
✓ 需避免循環參考導致記憶體洩漏 (父子雙向參考)
✗ 一般業務物件、生命週期明確受控的物件
✗ 高頻存取、低延遲要求的熱路徑
✗ 需序列化/跨邊界傳遞的物件