首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> ASP.NET 基礎 >> ASP.NET 基礎

.NET WeakReference 弱參考完整指南:讓 GC 在記憶體壓力時回收物件

     編輯:ASP.NET 基礎

強參考 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?決策清單


✓ 物件建立成本高、佔用記憶體大 (大圖片、報表、快取資料)
✓ 物件非持續需要,可重建 (可捨棄式快取)
✓ 發布-訂閱模式,發布者不應阻止訂閱者被回收
✓ 需避免循環參考導致記憶體洩漏 (父子雙向參考)

✗ 一般業務物件、生命週期明確受控的物件
✗ 高頻存取、低延遲要求的熱路徑
✗ 需序列化/跨邊界傳遞的物件
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved