11

假设我有

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

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

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

4

5 回答 5

8

一旦确定它们将不再被使用,对象就可以成为垃圾回收的条件。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 。输出是一样的。

于 2012-07-13T21:57:35.840 回答
3

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

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

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

强调我的。

于 2012-07-13T22:01:54.563 回答
1

如果不定义您所指的 CLR 版本,就很难确定您将在此处看到的行为。

在此示例中,假设的 CLR 可以假设以下情况为真

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

完全无视这条线Bar bar = new Bar();并优化它,因为它“什么都不做”。

就我的记忆而言,在当前版本的 CLRbar中,在您构建它的那一刻就有资格进行垃圾收集。

于 2012-07-13T22:01:20.550 回答
1

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

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
}
于 2012-07-13T22:36:55.773 回答
1

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

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 在其完成构建之前最终确定的概率。

于 2012-07-13T22:55:45.457 回答