C#GC.Collect如果使用实例构造函数初始化程序构造它,则不会销毁该对象

时间:2013-01-24 04:29:21

标签: c# .net garbage-collection weak-references

  

可能重复:
  Resurrection difference in using Object Initializer

我很难理解垃圾收集器在C#中是如何工作的(我正在使用2012,所以c#4.5)。这是我的示例代码:

    public class A
    {
        public int c;
        public A(){}
        public A(int pC)
        {
            c = pC;
        }
    }

    public static void Main()
    {
        // Test 1
        var a = new A {c=199};
        var aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
        //            Console.WriteLine(GC.GetGeneration(aRef.Target)); //output 1

        // Test 2
        a = new A (200);
        aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
    }

输出为True / True / True / False

在两个测试中,在我看来,在调用GC.Collect之前,堆上的对象没有root。但实际上,在测试1中,对象通过强制gc运行,而在测试2中却没有。那么,使用初始化器有什么神秘感吗? 我的猜测是,当使用初始化程序时,可能会有“一些额外的代码”,这将成为同一个对象的强根......

感谢。

3 个答案:

答案 0 :(得分:5)

使用初始化程序时说

 var a = new A {c=199}; --------> 1

编译器在堆栈上包含一个额外的引用,使对象通过GC 上述陈述(1)结果如下

 var temp = new A() ;
  temp.c=199;
  var a=temp . 

我认为这个临时变量使这个对象在GC期间生效。

请参阅此link

编辑:正如评论中 TomTom 所述。如果调试器正在运行,那么GC行为将被更改并且变量保持活动直到方法结束,直到最后一次使用。如果我错了,请纠正我

答案 1 :(得分:4)

显然,您正在运行Debug构建或附加调试器。垃圾收集器从即时编译器获取生命周期提示,它生成一个表,指示可以引用局部变量的代码段。垃圾收集器遍历由GC中断的执行方法的堆栈,并根据此表检查执行位置。并且在找到匹配时将引用计为有效。

如果代码是在Debug配置中构建的,或者在附加调试器时,抖动会修改此表并让变量保持活动状态直到方法体结束。这使得调试代码变得更加容易,您可以将局部变量放在监视表达式中,即使您超过不再使用该变量的点,它也会产生结果。

@Imposter发布的答案是正确的,隐藏的临时变量使A的第一个实例保持活动状态。并且垃圾收集器认为它有效,直到方法结束,因为您正在使用调试器。您的第二个a = null;分配允许第二个实例被垃圾收集。

在生产中运行此代码时会发生什么,这是非常不同的。首先,抖动优化器将删除 a = null分配。它知道那些赋值没有有用的副作用,因此它不会为它们生成代码。非常不直观,最好的方法是采取以下步骤:

  • 从代码中删除a = null分配
  • 使用Build + Configuration Manager切换到Release配置
  • 使用工具+选项,调试,常规,取消选中“在模块加载时抑制JIT优化”选项。

最后一个选项更改允许您继续使用调试器,而不会影响抖动生成代码的方式。现在临时变量将不再保留引用的第一个A实例,由抖动生成的表只会将其标记为存储方法中第一个语句的有效引用。运行你的程序,你会看到:

True
False
True
False

有了重要的新见解,即实际上不需要设置对null的引用,垃圾收集器足够聪明,不需要您提供帮助。

答案 2 :(得分:0)

我非常相信堆栈上的额外引用会产生GC Root。

让我们通过SOS调试来确认..

  1. 使用初始化程序时,

      (before GC.Collect)
      01bdc110 - object address of A.
    
      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68
      ESP:20e960:Root:  01bdc110(GCTest.A)
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) -- extra stack reference generated by compiler**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)
      ESP:20ebc4:Root:  01bdc110(GCTest.A)
      Scan Thread 2404 OSTHread 964
    

    (GC.Collect之后)

      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68       
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) ----//Still remains**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)         
      Scan Thread 2404 OSTHread 964
    

    确认未删除弱引用。

       !GCRoot 01bdc210 //(address of weak reference)
       Scan Thread 2920 OSTHread b68
       **ESP:20e968:Root:  01bdc210(System.WeakReference) // Created by us** 
       ESP:20ebb4:Root:  01bdc210(System.WeakReference) // other in mscorlib
       ESP:20ebc0:Root:  01bdc210(System.WeakReference) // other in mscorlib
       Scan Thread 2404 OSTHread 964
    
  2. 2,没有初始化程序,

        !GCRoot 01bdd4e0 
        Scan Thread 2920 OSTHread b68
        ESP:20e960:Root:  01bdd4e0(GCTest.A)
        ESP:20eba8:Root:  01bdd4e0(GCTest.A)
        ESP:20ebc4:Root:  01bdd4e0(GCTest.A)
        Scan Thread 2404 OSTHread 964
    
       **No extra stack reference.**