C# 32位和64位应用程序之间的GC行为不一致

C# 32位和64位应用程序之间的GC行为不一致,c#,.net,.net-4.0,garbage-collection,finalizer,C#,.net,.net 4.0,Garbage Collection,Finalizer,我注意到,在使用VS2013在.NET4.0的32位和64位下编译控制台应用程序时,GC的行为不一致 考虑以下代码: class Test { public static bool finalized = false; ~Test() { finalized = true; } } 在Main() var t = new Test(); t = null; GC.Collect(); GC.WaitForPendingFinalizers();

我注意到,在使用VS2013在.NET4.0的32位和64位下编译控制台应用程序时,GC的行为不一致

考虑以下代码:

class Test
{
    public static bool finalized = false;
    ~Test()
    {
        finalized = true;
    }
}
Main()

var t = new Test();
t = null;
GC.Collect();
GC.WaitForPendingFinalizers();
if (!Test.finalized)
    throw new Exception("oops!");
在64位(调试)模式下运行时,每次都能正常工作;但是,在32位模式下运行时,我无法强制收集此对象(即使我创建了更多对象并等待了一段时间,我已经尝试过)

有人知道为什么会这样吗?在调试一些必须处理为32位版本的程序集释放非托管代理数据的代码时,这给我带来了麻烦。在32位模式下,有很多对象只是在很长一段时间后才会出现(在64位模式下则不然)

我试图在32位模式下调试一些东西,但是没有调用终结器(至少不是通过强制方式)。对象只是坐在那里,永远不会被收集(我可以看到所有弱引用仍然有一个值)。在64位模式下,所有弱引用按预期清除,并调用所有终结器

注意:尽管上面的代码规模很小,但我注意到在32位模式下,更多的对象被卡在GC中,直到稍后创建更多的对象(即使调用了“Collect”和“WaitForPendingFinalizers”)。在64位模式下,情况永远不是这样。我有一个用户想知道为什么有这么多的对象没有被收集,这让我进行了调查,我发现在64位模式下,一切似乎都比在32位模式下工作得更好。只是想弄明白为什么

编辑:以下是更好的代码来显示差异:

class Program
{
    class Test
    {
        public static bool Finalized = false;
        public int ID;
        public Test(int id)
        {
            ID = id;
        }
        ~Test()
        { // <= Put breakpoint here
            Finalized = true;
            Console.WriteLine("Test " + ID + " finalized.");
        }
    }

    static List<WeakReference> WeakReferences = new List<WeakReference>();

    public static bool IsNet45OrNewer()
    {
        // Class "ReflectionContext" exists from .NET 4.5 onwards.
        return Type.GetType("System.Reflection.ReflectionContext", false) != null;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
        Console.WriteLine("IntPtr: " + IntPtr.Size + Environment.NewLine);

        Console.WriteLine("Creating the objects ...");

        for (var i = 0; i < 10; ++i)
            WeakReferences.Add(new WeakReference(new Test(i)));

        Console.WriteLine("Triggering collect ...");
        GC.Collect();

        Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
        GC.WaitForPendingFinalizers();

        Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");

        bool ok = true;

        for (var i = 0; i < 10; ++i)
            if (WeakReferences[i].IsAlive)
            {
                var test = (Test)WeakReferences[i].Target;
                if (test != null)
                    Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
                ok = false;
            }

        if (ok)
            Console.WriteLine("All Test objects successfully collected and finalized.");

        Console.WriteLine(Environment.NewLine + "Creating more objects ...");

        for (var i = 0; i < 10; ++i)
            WeakReferences.Add(new WeakReference(new Test(i)));

        Console.WriteLine("Triggering collect ...");
        GC.Collect();

        Console.WriteLine("Triggering finalizers ..." + Environment.NewLine);
        GC.WaitForPendingFinalizers();

        Console.WriteLine(Environment.NewLine + "Checking for objects still not finalized ...");

        ok = true;

        for (var i = 0; i < 10; ++i)
            if (WeakReferences[i].IsAlive)
            {
                var test = (Test)WeakReferences[i].Target;
                if (test != null)
                    Console.WriteLine("Weak references still exist for Test " + test.ID + ".");
                ok = false;
            }

        if (ok)
            Console.WriteLine("All Test objects successfully collected and finalized.");

        Console.WriteLine(Environment.NewLine + "Done.");

        Console.ReadKey();
    }
}
类程序
{
课堂测试
{
公共静态bool=false;
公共int ID;
公共测试(int-id)
{
ID=ID;
}
~Test()

{//在x86和x64上的VS2013+.NET 4.5上无需复制:

class Program
{
    class Test
    {
        public readonly int I;

        public Test(int i)
        {
            I = i;
        }

        ~Test()
        {
            Console.WriteLine("Finalizer for " + I);
        }
    }

    static void Tester()
    {
        var t = new Test(1);
    }

    public static bool IsNet45OrNewer()
    {
        // Class "ReflectionContext" exists from .NET 4.5 onwards.
        return Type.GetType("System.Reflection.ReflectionContext", false) != null;
    }

    static void Main(string[] args)
    {
        Console.WriteLine("Is 4.5 or newer: " + IsNet45OrNewer());
        Console.WriteLine("IntPtr: " + IntPtr.Size);

        var t = new Test(2);
        t = null;

        new Test(3);

        Tester();

        Console.WriteLine("Pre GC");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("Post GC");

        Console.ReadKey();
    }
}
请注意(正如@Sriram Sakthivel所注意到的),可能存在变量的“重影”副本,它们的生命周期会延长到方法结束(当您在调试模式下编译代码或附加了调试器(F5而不是Ctrl+F5)时),变量的寿命将延长到方法结束,以便于调试)


例如,对象初始化(当您执行new Foo{I=5}之类的操作时)会创建一个隐藏的局部变量。

您是否尝试过向~Test()添加一些代码?也许空终结器不知何故被忽略了。您是否在调试器中运行?它会做很多事情来简化调试,包括延长局部变量的生存期。如果您在调试器之外运行,或者退出该方法,问题是否仍然存在?您确定没有剩余的强引用吗?这就是具有问题是,基本相同,在32位模式控制台应用程序中失败。实际代码在“\gctest”中V8.Net测试控制台应用程序的选项。尝试在与Main不同的方法中创建对象。通过一个人为的示例,您可以证明人为的问题。您确定终结器并没有被优化掉吗?我的意思是,它什么都不做,为什么您会拥有它?在调试器下,变量的生存期(即使是你看不到的临时变量)扩展到方法的结尾。32位和64位不同,调试器与32位和64位程序的交互方式也略有不同。您应该创建一个示例,显示差异,而不在调试器下运行。您显示的是不同的。您显示了一个对象初始值设定项示例,该对象初始值设定项具有隐藏本地。您应该尝试OP的精确代码。它不会扩展到方法的结尾。正如我前面所说的,它不等于OP的代码。对象初始值设定项将创建一个隐藏本地,该本地未设置为null。因此对象是可访问的(通过隐藏本地)。对于OP的代码,这不是一个问题,变量将变得不可访问。事实上,我无法复制它,并投票关闭它,因为无法复制。@SriramSakthivel你是对的。现在它是一个不可复制的相反意义…调试器总是最终确定对象。@JamesWilkins甚至针对.NET 4.0,它将使用的运行时是4.5。(但为了确保我已经检查过了)我修改了代码,以显示它是使用.NET 4.5还是4.0。遗憾的是,我没有任何一台没有.NET 4.5的Visual Studio机器。我已将语言版本强制设置为3.0、4.0和5.0(在高级设置下),x86中的所有语言都无法收集。