You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ConditionalWeakTable与GC.Collect()行为异常:WeakReference.Target未回收原因排查

为什么第二次GC.Collect()后WeakReference仍存活?这不是Bug,问题出在循环引用

这不是.NET的Bug,核心原因是委托实例与目标对象通过ConditionalWeakTable形成了循环强引用,导致GC无法回收这两个对象。

测试结果

weakRef.Target is alive = True, expected true because inst keep a hold on SomeClass.
weakRef.Target is alive = True, expected false, because there is no more ref on SomeClass.

测试代码

public static class DelegateKeeper
{
    private static ConditionalWeakTable<object, Action> cwtAction = new ConditionalWeakTable<object, Action>();
    public static void KeepAlive(Action action) => cwtAction.Add(action.Target, action);
}

public class SomeClass
{
    public void DoSomething() { }
}

public static class GcHelper
{
    public static void Collect()
    {
        // OK surely overkill but just to make sure. I will reduce it when everyting will be understood.
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
        GC.WaitForPendingFinalizers();

        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
        GC.WaitForPendingFinalizers();
    }
}

SomeClass instanceSomeClass;
WeakReference<Action> weakRef;

[TestMethod]
public void TestLifeOfObject()
{
    Init();
    GcHelper.Collect();
    Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected true because inst keep a hold on SomeClass.");

    RemoveLastReferenceOnSomeClass();
    GcHelper.Collect();
    Debug.WriteLine($"weakRef.Target is alive = {weakRef.TryGetTarget(out _)}, expected false, because there is no more ref on SomeClass.");
}

private void Init()
{
    instanceSomeClass = new SomeClass();
    var action = instanceSomeClass.DoSomething;
    weakRef = new WeakReference<Action>(action);
    DelegateKeeper.KeepAlive(action);
}

private void RemoveLastReferenceOnSomeClass()
{
    instanceSomeClass = null;
}

问题拆解

  1. 委托的强引用特性:instanceSomeClass.DoSomething生成的委托实例action,内部会自动持有对instanceSomeClass(目标对象)的强引用——这是实例方法委托的固有特性,用来保证调用方法时目标对象存在。
  2. ConditionalWeakTable的行为:DelegateKeeper中的ConditionalWeakTable以action.Target(即instanceSomeClass)为弱键,action本身为强值。CWT的规则是:只要弱键对应的对象还存活,就会通过强引用保留对应的value;只有当弱键对象被GC判定为不可达时,才会移除条目并释放value。
  3. 循环引用的形成:当你把instanceSomeClass置为null后,外部对目标对象的强引用消失,但此时:
    • action仍然强引用instanceSomeClass;
    • CWT因为instanceSomeClass还存活,所以继续强引用action;
    • 这就形成了一个闭环:instanceSomeClass存活 → action被CWT保留 → instanceSomeClass被action强引用而存活。
  4. GC无法打破循环:GC回收循环引用的前提是,循环内的对象没有外部强引用。但这里的循环依赖CWT的弱键存活状态,GC无法判定instanceSomeClass为不可达——因为action还在引用它,而action的存活又依赖于instanceSomeClass的存活,最终导致两者都无法被回收,weakRef.Target(即action)自然也会保持存活。

解决思路

如果需要让目标对象和委托能被正常回收,要避免CWT的value持有对弱键的强引用。比如,不要直接存储action,而是拆分委托的方法信息和目标对象,只存储不依赖目标对象存活的部分(比如仅存储MethodInfo),但这样会丢失实例方法的上下文,需要根据实际业务场景调整逻辑。

内容的提问来源于stack exchange,提问作者Eric Ouellet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 15:10:25