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

标签 c# .net .net-4.0 garbage-collection finalizer

在使用 VS 2013 在 .Net 4.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 位模式下调试某些东西,但没有调用终结器(至少,不是强制调用)。对象只是坐在那里,永远不会被收集(我可以看到所有弱引用仍然具有值(value))。在 64 位模式下,所有弱引用都按预期清除,并调用所有终结器。

注意:虽然上面的代码规模很小,但我注意到在 32 位模式下,许多 更多 对象卡在 GC 中,直到稍后创建更多对象(即使在“收集”时)并调用“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();
    }
}

它适用于 64 位,但不适用于 32 位。在我的系统上,即使在创建更多对象并尝试第二次之后,“测试 #9”也永远不会被收集(弱引用仍然存在)。

仅供引用:问这个问题的主要原因是因为我的控制台中有一个 \gctest 选项,用于测试 V8.Net 和 native 端的底层 V8 引擎之间的垃圾收集。它适用于 64 位,但不适用于 32 位。

最佳答案

在 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 所注意到的),周围可能存在变量的“幽灵”副本,它们的生命周期延长到方法结束(当您在 Debug模式下编译代码时,或者您有一个附加调试器(F5 而不是 Ctrl+F5),变量的生命周期延长到方法结束以简化调试)

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

关于c# - 32 位和 64 位应用程序之间的 GC 行为不一致,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30254539/

相关文章:

asp.net - 在 Amazon Web Service 中托管 ASP.NET 应用程序

.net - .NET 中的事件和内存泄漏

.net - NET 4.0 在 COM+ 中安装程序集加载额外的依赖项

c# - ASP.NET Core 2.1 角色管理器注册

c# - 如何使用 knockout、jquery 和 ASP.NET MVC 创建可重用的控件?

c# - 使用 LINQ TO SQL 选择单行

c# - 是否可以在不尝试对基类类型的每个派生类类型进行强制转换的情况下再次向下转换对象?

.net - 减少 Angular 8 中的 Vendor.js 文件大小

unit-testing - Visual Studio 2012 测试项目混合模式运行时

c# - C#编译不安全代码中的错误