12

可能重复:
使用对象初始化器的复活差异

我很难理解垃圾收集器在 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);
    }

输出为真/真/真/假

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

谢谢。

4

3 回答 3

5

使用初始化程序时说

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

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

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

我认为这个临时变量使这个对象在 GC 期间还活着。

请参考这个链接

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

于 2013-01-24T07:26:07.027 回答
4

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

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

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

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

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

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

True
False
True
False

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

于 2013-01-24T13:13:09.667 回答
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,没有初始化器,

    !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.**
于 2013-01-24T12:32:42.380 回答