Когда и как объекты классов и структур удаляются из памяти в C#
Что проверяют
Различаете ли вы значимые и ссылочные типы по тому, где живут данные, а не по заученной фразе «структуры на стеке». Следом обычно спрашивают про поколения сборщика, IDisposable и то, откуда в управляемом коде берутся утечки.
Как отвечать
Объекты классов лежат в управляемой куче, удалить их вручную нельзя. Сборщик мусора освобождает память, когда объект недостижим от корней: локальных переменных и регистров, статических полей, GCHandle. Момент сборки недетерминирован: GC запускается, когда исчерпан бюджет поколения или системе не хватает памяти, а не в момент потери последней ссылки.
- поколения: новые объекты попадают в поколение 0, пережившие сборку переходят в 1 и 2. Нулевое собирается часто и дёшево, второе редко и дорого;
- LOH: объекты от 85 000 байт идут в кучу больших объектов, она собирается вместе со вторым поколением и по умолчанию не уплотняется;
- финализаторы: объект с финализатором (
~MyClass()) переживает лишнюю сборку, пока его финализатор не выполнит отдельный поток. Неуправляемые ресурсы освобождают черезDispose()иusing, а вDisposeвызываютGC.SuppressFinalize(this).
Структура хранится там, где объявлена. Локальная переменная живёт в кадре стека или в регистре и исчезает вместе с ним. Поле класса, элемент массива, переменная, захваченная лямбдой, или локальная переменная async-метода, пережившая await, лежат в куче внутри владельца и уходят вместе с ним. Упаковка создаёт отдельный объект, его соберёт GC. Жить только на стеке гарантированно может лишь ref struct, например Span<T>.
Где спотыкаются
Говорят, что циклические ссылки вызывают утечки. Сборщик в .NET трассирующий: два объекта, ссылающихся друг на друга, но недостижимых от корней, соберутся.
Путают Dispose с удалением объекта из памяти. Dispose освобождает ресурс, например файловый дескриптор, а память под объектом всё равно заберёт GC.
Не называют реальные источники утечек: подписку на событие долгоживущего объекта без отписки, статические коллекции и кэши без вытеснения, таймеры, которые держат ссылку на обработчик.