Кто отвечает за очистку кучи в C#?
Что проверяют
Ответ «сборщик мусора» даёт каждый, поэтому проверяют глубину: как GC решает, что объект мёртв, что такое поколения и где заканчивается его зона ответственности. Следом почти всегда спрашивают про IDisposable и финализаторы или просят назвать причину утечки памяти в управляемом коде.
Как отвечать
Управляемой кучей занимается сборщик мусора CLR. Работает он не подсчётом ссылок, а обходом графа: от корней, то есть локальных переменных в стеках потоков, статических полей и хэндлов GC, помечает всё достижимое, остальное считает мусором, затем уплотняет кучу и обновляет ссылки.
Ключевые детали:
- поколения: 0, 1 и 2. Новые объекты попадают в поколение 0, пережившие сборку переходят дальше. Сборка поколения 0 частая и дешёвая, полная сборка с поколением 2 редкая и дорогая;
- LOH: объекты от 85 000 байт идут в кучу больших объектов, она собирается вместе с поколением 2 и по умолчанию не уплотняется;
- когда запускается: исчерпан бюджет поколения 0, система сообщает о нехватке памяти или вызван
GC.Collect(); - режимы: Workstation и Server GC плюс фоновая сборка поколения 2, при которой приложение продолжает работать. Серверный режим включается
<ServerGarbageCollection>true</ServerGarbageCollection>в проекте, в ASP.NET Core он стоит по умолчанию.
Граница ответственности: GC освобождает память, но не закрывает файлы, сокеты и соединения с базой. Для этого IDisposable и using, в том числе using var без блока. Финализатор нужен только классу, который напрямую держит неуправляемый ресурс, и такой ресурс лучше завернуть в SafeHandle.
Смотреть поведение GC в работающем приложении удобно через dotnet-counters и dotnet-gcdump.
Где спотыкаются
Говорят про подсчёт ссылок. Так устроены COM и Swift, а .NET использует трассирующий сборщик, поэтому циклические ссылки ему не мешают.
Вызывают GC.Collect() «для надёжности». Это полная блокирующая сборка, которая продвигает живые объекты в старшие поколения и обычно ухудшает картину.
Считают, что утечек в C# не бывает. Подписка на событие долгоживущего объекта или статический кэш без ограничения держат объекты живыми, пока живо приложение.