.NET垃圾收集器是否执行代码的预测分析?

时间:2010-07-01 19:41:43

标签: c# .net garbage-collection jit

好的,我意识到这个问题可能看起来很奇怪,但我只是注意到一些令我困惑的事情......看看这段代码:

static void TestGC()
{
        object o1 = new Object();
        object o2 = new Object();
        WeakReference w1 = new WeakReference(o1);
        WeakReference w2 = new WeakReference(o2);

        GC.Collect();

        Console.WriteLine("o1 is alive: {0}", w1.IsAlive);
        Console.WriteLine("o2 is alive: {0}", w2.IsAlive);
}

由于o1o2在垃圾收集发生时仍在范围内,我希望得到以下输出:

  

o1还活着:真实   o2还活着:真的

但相反,这就是我得到的:

  

o1还活着:假   o2还活着:错误

注意:仅当代码在发布模式下编译并在调试器外部运行时才会出现

我的猜测是,GC检测到o1o2在超出范围之前不会再次使用,并提前收集它们。为了验证这个假设,我在TestGC方法的末尾添加了以下行:

string s = o2.ToString();

我得到了以下输出:

  

o1还活着:假   o2还活着:真的

因此,在这种情况下,不会收集o2

有人可以了解正在发生的事情吗?这与JIT优化有关吗?究竟发生了什么?

4 个答案:

答案 0 :(得分:23)

垃圾收集器依赖于JIT编译器提供的编译到程序集中的信息,它告诉它什么代码地址范围各种变量和“东西”仍在使用中。

因此,在您的代码中,由于您不再使用对象变量,GC可以自由地收集它们。 WeakReference不会阻止这一点,事实上,这是WR的全部要点,允许你保持对一个对象的引用,同时不阻止它被收集。

有关WeakReference个对象的案例很好地总结在MSDN的单行描述中:

  

表示一个弱引用,它引用一个对象,同时仍然允许通过垃圾回收来回收该对象。

WeakReference对象不是垃圾收集的,因此您可以安全地使用它们,但它们引用的对象只剩下WR引用,因此可以自由收集。

当通过调试器执行代码时,变量被人为地扩展到范围,直到它们的范围结束,通常是它们被声明的块的结尾(如方法),以便您可以在断点处检查它们。 / p>

有一些微妙的事情要发现。请考虑以下代码:

using System;

namespace ConsoleApplication20
{
    public class Test
    {
        public int Value;

        ~Test()
        {
            Console.Out.WriteLine("Test collected");
        }

        public void Execute()
        {
            Console.Out.WriteLine("The value of Value: " + Value);

            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();

            Console.Out.WriteLine("Leaving Test.Execute");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Test t = new Test { Value = 15 };
            t.Execute();
        }
    }
}

在Release模式下,在没有附加调试器的情况下执行,这是输出:

The value of Value: 15
Test collected
Leaving Test.Execute

这样做的原因是,即使您仍在执行与Test对象关联的方法内部,在要求GC执行此操作时,也不需要对Test进行任何实例引用(不参考thisValue),并且不会调用任何实例方法,因此可以安全地收集对象。

如果您不了解它,可能会产生一些令人讨厌的副作用。

考虑以下课程:

public class ClassThatHoldsUnmanagedResource : IDisposable
{
    private IntPtr _HandleToSomethingUnmanaged;

    public ClassThatHoldsUnmanagedResource()
    {
        _HandleToSomethingUnmanaged = (... open file, whatever);
    }

    ~ClassThatHoldsUnmanagedResource()
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        (release unmanaged resource here);
        ... rest of dispose
    }

    public void Test()
    {
        IntPtr local = _HandleToSomethingUnmanaged;

        // DANGER!

        ... access resource through local here
    }

此时,如果Test在获取非托管句柄的副本后没有使用任何实例数据,该怎么办?如果GC现在在我写“危险”的地方运行怎么办?你知道这是怎么回事吗?当GC运行时,它将执行终结器,它将从正在执行的Test下取出对非托管资源的访问权。

通常通过IntPtr或类似方式访问的非托管资源对垃圾收集器不透明,在判断对象的生命周期时不会考虑这些资源。

换句话说,我们在局部变量中保留对句柄的引用对于GC是没有意义的,它只会注意到没有实例引用,因此认为该对象是安全的。

这个if课程假设没有外部引用仍然被认为是“活着”的对象。例如,如果上述类是从这样的方法中使用的:

public void DoSomething()
{
    ClassThatHoldsUnmanagedResource = new ClassThatHoldsUnmanagedResource();
    ClassThatHoldsUnmanagedResource.Test();
}

然后你有完全相同的问题。

(当然,您可能不应该像这样使用它,因为它实现了IDisposable,您应该使用using - 阻止或手动调用Dispose。)

编写上述方法的正确方法是强制GC在我们仍然需要时不会收集我们的对象:

public void Test()
{
    IntPtr local = _HandleToSomethingUnmanaged;

    ... access resource through local here

    GC.KeepAlive(this); // won't be collected before this has executed
}

答案 1 :(得分:12)

垃圾收集器从JIT编译器获取生命周期提示。因此,它知道在GC.Collect()调用时,不再有可能引用局部变量,因此可以收集它们。查看GC.KeepAlive()

附加调试器时,将禁用JIT优化,并将生命周期提示扩展到方法的末尾。使调试变得更加简单。

我写了一个更详细的答案,你会发现it here

答案 2 :(得分:0)

我不是C#专家但我会说这是因为在制作中你的代码是优化的

static void TestGC()
{
        WeakReference w1 = new WeakReference(new Object());
        WeakReference w2 = new WeakReference(new Object());

        GC.Collect();

        Console.WriteLine("o1 is alive: {0}", w1.IsAlive);
        Console.WriteLine("o2 is alive: {0}", w2.IsAlive);
}

不再有o1,o2结合仍然存在。

编辑:这是一个名为常量折叠的编译器优化。 无论是否有JIT都可以做到这一点。

如果您有办法禁用JIT但保留了发布优化,那么您将会遇到相同的行为。

人们应该注意这个说明:

  

注意:这仅在代码时发生   在发布模式下编译并运行   在调试器外面

这是关键。

PS:我也假设您了解WeakReference的含义。

答案 3 :(得分:0)

  

有人可以了解正在发生的事情吗?

虚拟机垃圾收集资源的心理模型非常简单。特别是,您假设变量和范围与垃圾收集有某种关系是错误的。

  

这与JIT优化有关吗?

  

到底发生了什么?

JIT没有打扰不必要的引用,所以跟踪收集器在踢它时没有找到引用,因此它收集了对象。

请注意,其他答案表明,当事实上JIT 没有将这些引用传达给GC时,JIT将此信息传达给GC,因为这样做是没有意义的