Поведение GC несовместимо между 32-битными и 64-битными приложениями

Я заметил несогласованное поведение GC при компиляции консольных приложений как в 32-битной, так и в 64-битной среде в.Net 4.0 с использованием VS 2013.

Рассмотрим следующий код:

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-разрядном режиме гораздо больше объектов зависло в ГХ, пока в дальнейшем не будет создано больше объектов (даже при вызове "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();
    }
}

Он работает в 64-битной, но не в 32-битной. В моей системе "Тест №9" никогда не собирается (остается слабая ссылка), даже после создания большего количества объектов и повторной попытки.

К вашему сведению: главная причина задавать вопрос, потому что у меня есть \gctest вариант в моей консоли, чтобы проверить сборку мусора между V8.Net и базовым движком V8 на родной стороне. Работает в 64-битной, но не 32-битной версии.

1 ответ

Нет репродукций на VS2013 + .NET 4.5 на x86 и x64:

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 }) создает скрытую локальную переменную.

Другие вопросы по тегам