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

标签 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 is alive: True
o2 is alive: True

但相反,这是我得到的:

o1 is alive: False
o2 is alive: False

注意:仅当代码在 Release 模式下编译并在调试器之外运行时才会发生这种情况

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

string s = o2.ToString();

我得到了以下输出:

o1 is alive: False
o2 is alive: True

所以在这种情况下,o2 不会被收集。

有人可以阐明发生了什么事吗?这与 JIT 优化有关吗?到底发生了什么?

最佳答案

垃圾收集器依赖于 JIT 编译器提供的编译到您的程序集中的信息,这些信息告诉它哪些代码地址范围内的各种变量和“事物”仍在使用中。

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

关于WeakReference的案例objects 在 MSDN 上的一行描述中得到了很好的总结:

Represents a weak reference, which references an object while still allowing that object to be reclaimed by garbage collection.

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

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

用这个可以发现一些微妙的东西。考虑以下代码:

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

The reason for this is that even though you're still executing inside a method associated with the Test object, at the point of asking GC to do it's thing, there is no need for any instance references to Test (no reference to this or Value), and no calls to any instance-method left to perform, so the object is safe to collect.

This can have some nasty side-effects if you're not aware of it.

Consider the following class:

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 现在在我写“DANGER”的地方运行怎么办?你知道这是怎么回事吗?当 GC 运行时,它将执行终结器,这将从仍在执行的 Test 中抽出对非托管资源的访问。

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

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

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

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

那么你有完全相同的问题。

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

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

public void Test()
{
    IntPtr local = _HandleToSomethingUnmanaged;

    ... access resource through local here

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

关于c# - .NET 垃圾收集器是否执行代码的预测分析?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3161119/

相关文章:

c# - Linq to SQL 与序列化

c# - .NET 4.0 中的动态 : am I doing it right?

c# - 如何查看邮件是否发送成功

c# - IValueConverter 和可见性

debugging - 如何可视化堆转储?

c# - 有没有办法测试一个潜在的僵尸交易,看看它是否可以回滚?

c# - 无法更改 javascript 中设置的 cookie 值?

c# - .NET 类型的私有(private)成员的命名约定

java - java中过时的引用是什么意思?

Javascript GC 并将对象分配给 null