С# GC.Collect не уничтожить объект, если он создан с использованием инициализатора конструктора экземпляра

Возможный дубликат:
Разница Воскрешения при использовании Object Initializer

Мне трудно понять, как сборщик мусора работает на С# (я использую 2012, поэтому С# 4.5). Вот мой пример кода:

    public class A
    {
        public int c;
        public A(){}
        public A(int pC)
        {
            c = pC;
        }
    }

    public static void Main()
    {
        // Test 1
        var a = new A {c=199};
        var aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
        //            Console.WriteLine(GC.GetGeneration(aRef.Target)); //output 1

        // Test 2
        a = new A (200);
        aRef = new WeakReference(a);
        a = null;
        Console.WriteLine(aRef.IsAlive);
        GC.Collect();
        Console.WriteLine(aRef.IsAlive);
    }

Выход - True/True/True/False

Мне кажется, что в обоих тестах объект в куче не имеет корня перед вызовом GC.Collect. Но бывает так, что в тесте 1 объект проходит через силу gc, а в тесте 2 - нет. Итак, есть ли что-то загадочное в использовании инициализатора? Я предполагаю, что при использовании инициализатора может быть "некоторый дополнительный код", который станет сильным корнем для одного и того же объекта.....

Спасибо.

Ответ 1

Очевидно, что вы используете либо сборку Debug, либо подключен отладчик. Сборщик мусора получает подсказки на всю жизнь от компилятора "точно в срок", он генерирует таблицу, которая указывает, в каких разделах кода можно ссылаться на локальную переменную. Сборщик мусора выполняет стек метода выполнения, который был прерван GC и проверяет место выполнения этой таблицы. И подсчитывает ссылку как действительную, когда находит совпадение.

Если код был создан в конфигурации Debug или когда подключен отладчик, джиттер изменяет эту таблицу и позволяет переменной оставаться в живых до конца тела метода. Это значительно облегчает отладку кода, вы можете поместить локальную переменную в выражение часов, и это даст результат, даже если вы перейдете к точке, где переменная больше не используется.

Ответ, отправленный @Imposter, верен, скрытая временная переменная сохраняет первый экземпляр A живым. И сборщик мусора считает его действительным до конца метода, потому что вы используете отладчик. Второе задание a = null; позволяет второму экземпляру собирать мусор.

То, что действительно произойдет, когда вы запустите этот код в процессе производства, сильно отличается. Во-первых, оптимизатор джиттера будет удалять присвоения a = null. Он знает, что эти назначения не имеют полезных побочных эффектов, поэтому он не создает для них никакого кода. Довольно неинтуитивный, лучший способ увидеть это - сделать следующие шаги:

  • Удалить назначение a = null из кода
  • Переключитесь в конфигурацию Release с помощью диспетчера конфигурации Build +.
  • Используйте "Инструменты + Опции", "Отладка", "Общие", отключите опцию "Подавить оптимизацию JIT при загрузке модуля".

Последнее изменение параметра позволяет вам продолжать использовать отладчик, не влияя на то, как дрожание генерирует код. Теперь временная переменная больше не будет содержать первый экземпляр ссылки A, таблица, сгенерированная дрожанием, будет отмечать ее только как хранилище действительной ссылки для первого оператора в методе. Запустите программу, и вы увидите:

True
False
True
False

С важным новым пониманием, что установка ссылки на нуль на самом деле не требуется, сборщик мусора достаточно умен, чтобы не потребовать от вас помощи.

Ответ 2

При использовании инициализатора скажите

 var a = new A {c=199}; --------> 1
Компилятор

содержит дополнительную ссылку на стек, которая заставляет объект проходить через GC.
Утверждение (1) в приведенном выше виде выглядит следующим образом

 var temp = new A() ;
  temp.c=199;
  var a=temp . 

Я думаю, что эта временная переменная делает этот объект живым во время GC.

Пожалуйста, обратитесь к этой ссылке

EDIT: как указано в TomTom в комментариях. Если отладчик работает, то поведение GC будет изменено, и переменные сохраняются в живых до конца метода, но не до последнего использования. Пожалуйста, поправьте меня, если я ошибаюсь.

Ответ 3

Я решительно полагаю, что дополнительная ссылка на стек, которая делает GC Root.

Пусть подтверждают SOS Debugging..

  • При использовании инициализатора

      (before GC.Collect)
      01bdc110 - object address of A.
    
      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68
      ESP:20e960:Root:  01bdc110(GCTest.A)
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) -- extra stack reference generated by compiler**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)
      ESP:20ebc4:Root:  01bdc110(GCTest.A)
      Scan Thread 2404 OSTHread 964
    

    (После GC.Collect)

      !GCRoot 01bdc110 
      Note: Roots found on stacks may be false positives. Run "!help gcroot" for more info.
      Scan Thread 2920 OSTHread b68       
      **ESP:20ebb8:Root:  01bdc110(GCTest.A) ----//Still remains**
      ESP:20ebbc:Root:  01bdc110(GCTest.A)         
      Scan Thread 2404 OSTHread 964
    

    Подтвердите, что слабая ссылка не удалена.

       !GCRoot 01bdc210 //(address of weak reference)
       Scan Thread 2920 OSTHread b68
       **ESP:20e968:Root:  01bdc210(System.WeakReference) // Created by us** 
       ESP:20ebb4:Root:  01bdc210(System.WeakReference) // other in mscorlib
       ESP:20ebc0:Root:  01bdc210(System.WeakReference) // other in mscorlib
       Scan Thread 2404 OSTHread 964
    

2, Без инициализатора,

    !GCRoot 01bdd4e0 
    Scan Thread 2920 OSTHread b68
    ESP:20e960:Root:  01bdd4e0(GCTest.A)
    ESP:20eba8:Root:  01bdd4e0(GCTest.A)
    ESP:20ebc4:Root:  01bdd4e0(GCTest.A)
    Scan Thread 2404 OSTHread 964

   **No extra stack reference.**