【问题标题】:Special case lifetime analysis特殊情况寿命分析
【发布时间】:2012-07-13 19:09:49
【问题描述】:

假设我有

void foo () {
    Bar bar = new Bar(); // bar is never referred to after this line
    // (1)
    doSomethingWithoutBar();
}

在 (1) 处,对象 bar 是否指向 符合 的垃圾回收条件?还是bar 也必须超出范围?如果GC.CollectdoSomethingWithoutBar 调用,会有区别吗?

这与了解 Bar 是否具有 (C#) 析构函数或类似的东西有关。

【问题讨论】:

  • 我只是等着 Eric Lippert 过来照顾一下这个……
  • @StefanH,我想我已经看到 Eric 对一个与此非常相似的问题的回答(或他的博客文章),但我的 google-fu 现在失败了 =(
  • @Rob - 我知道!我试着在谷歌上搜索“C# 垃圾收集”,因为我认为他会是第一个结果。
  • 仅供参考,在这些情况下(或更常见的是,当非托管资源需要保持活动状态时)的常见做法是使用GC.KeepAlive()

标签: c# garbage-collection


【解决方案1】:

只要确定它们将不再被使用,对象就可以进行垃圾回收。 bar 完全有可能在变量超出范围之前被垃圾回收。

证明:

using System;

class Bar
{
    ~Bar() { Console.WriteLine("Finalized!"); }
}

class Program
{
    static void Main(string[] args)
    {
        Bar bar = new Bar();
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Press any key to exit...");
        Console.ReadLine();
    }
}

发布模式下运行(因为它不会在调试模式下被收集)。

输出:

敲定! 按任何一个键退出...

它也适用于使用 Mono 的 ideone。输出是一样的。

【讨论】:

  • 我认为它会等待范围完成,以确保没有任何 runtime 对该本地对象的引用。
  • @Tigran,因为它是一个局部变量,在 runtime 怎么会有任何引用呢? =)
  • 我无法重现这种情况。在任何版本的框架中:2、3.0、3.5、4.0。
  • @Hailton:你试过在发布模式下运行它吗?我在发布模式下使用 Visual Studio C# 2010 Express (C# 4) 对其进行了测试。您可以单击 ideone 链接并查看输出 - 它也可以在那里工作。我还稍微简化了代码。您可以将其复制并粘贴到新的控制台项目中,切换到发布模式,然后按 F5。我使用这些说明将 Visual Studio Express 更改为在发布模式下构建:stackoverflow.com/a/3127008/61974
  • 是的,你是对的。在发布模式下,我能够重现这种情况。感谢您的澄清。我有一个问题:我做了一个类似的代码但不能做 GC 来收集对象。我将使用此代码作为一个新问题发布。
【解决方案2】:

通过快速阅读规范,它看起来像是特定于实现的。 允许垃圾收集它,但不是必需

我从ECMA Spec 的第 10.9 节“自动内存管理”中的注释中得到这个:

[注意:实现可能会选择分析代码以确定 将来可以使用对对象的哪些引用。为了 例如,如果范围内的局部变量是唯一存在的 引用一个对象,但从不引用该局部变量 在当前执行的任何可能的继续执行中 在过程中的点,一个实现可能(但不是必需的 to) 将对象视为不再使用。尾注]

强调我的。

【讨论】:

    【解决方案3】:

    如果不定义您所指的 CLR 版本,则不可能确定您将在此处看到的行为。

    一个假设的CLR可以,在这个例子中,假设以下是真的:

    1. Bar 的构造函数什么都不做
    2. 没有初始化的字段(即对象构造没有潜在的副作用)

    完全忽略 Bar bar = new Bar(); 行,并优化它,因为它“什么都不做”。

    就我的记忆而言,在当前版本的 CLR 中,bar 在您构建它之后就可以进行垃圾回收。

    【讨论】:

    • 我认为您的意思是“假设的编译器”
    • @StefanH,如果“假设的编译器”是指“假设的 JIT 编译器”,那么是的,这就是我的意思 =) 否则 Bar 可以在不同的程序集可以在csc 编译后换出,导致编译时的Bar 被剥离,即使运行时的Bar 会做一些事情..(认为插件开发是一种可能的情况)
    • 我更多的是认为它会在创建 CLR 之前被剥离,也就是在编译时,对吧?编辑:不,我错了(CLR 并不意味着我认为它的意思),我认为它可能会在生成 CIL 之前被剥离。
    • CLR = 公共语言运行时,即运行 csc/vbc 编译器生成的 MSIL/CIL 的“.net 虚拟机”。在我的假设场景中,Bar 可以在不同的程序集中定义,因此编译器无法将其剥离,因为它无法确定在运行时包含 Bar 的程序集是否具有相同的 Bar 实现.只有 JIT 编译器才能知道 =)
    • 谢谢,这很有意义。我很欣赏这个解释。将来我很可能会说“我会等罗布来这里照顾这个……”
    【解决方案4】:

    Marc 回答了这个问题,但这里是解决方案:

    void foo () {
        Bar bar = new Bar(); // bar is never referred to after this line
        // (1)
        doSomethingWithoutBar();
    
        GC.KeepAlive(bar); // At the point where you no longer need it
    }
    

    【讨论】:

      【解决方案5】:

      这肯定会发生。例如,这里有一个实例可以在您仍在执行其构造函数时完成的演示:

      class Program
      {
          private static int _lifeState;
          private static bool _end;
      
          private sealed class Schrodinger
          {
              private int _x;
      
              public Schrodinger()
              {
                  //Here I'm using 'this'
                  _x = 1;
      
                  //But now I no longer reference 'this'
                  _lifeState = 1;
      
                  //Keep busy to provide an opportunity for GC to collect me
                  for (int i=0;i<10000; i++)
                  {
                      var garbage = new char[20000];
                  }
      
                  //Did I die before I finished being constructed?
                  if (Interlocked.CompareExchange(ref _lifeState, 0, 1) == 2)
                  {
                      Console.WriteLine("Am I dead or alive?");
                      _end = true;
                  }
              }
      
              ~Schrodinger()
              {
                  _lifeState = 2;
              }
          }
      
          static void Main(string[] args)
          {
              //Keep the GC churning away at finalization to demonstrate the case
              Task.Factory.StartNew(() =>
                  {
                      while (!_end)
                      {
                          GC.Collect();
                          GC.WaitForPendingFinalizers();
                      }
                  });
      
              //Keep constructing cats until we find the desired case
              int catCount = 0;
              while (!_end)
              {
                  catCount++;
                  var cat = new Schrodinger();
                  while (_lifeState != 2)
                  {
                      Thread.Yield();
                  }
              }
              Console.WriteLine("{0} cats died in the making of this boundary case", catCount);
              Console.ReadKey();
          }
      }
      

      为了使其正常工作,您需要发出一个 Release 版本并在 Visual Studio 外部运行它(否则调试器会插入阻止该效果的代码。)我已经使用针对 .NET 4.0 x64 的 VS 2010 对此进行了测试。

      您可以调整“保持忙碌”循环中的迭代,以影响 Cat 在完成构建之前完成的概率。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-03
        • 2013-10-21
        • 2016-10-23
        • 2015-10-21
        • 2021-04-04
        • 1970-01-01
        相关资源
        最近更新 更多