Какие операции выполняет сборщик мусора при очистке
Что проверяют
Знаете ли вы, что сборщик мусора в .NET трассирующий и ссылки не считает, и можете ли пройти по фазам одной сборки. Следом обычно спрашивают про поколения, Large Object Heap и то, как финализатор продлевает жизнь объекту.
Как отвечать
Сборка запускается, когда исчерпан бюджет выделения в поколении 0, при явном GC.Collect() или при нехватке памяти в системе. Дальше идут шаги:
- приостановка потоков: управляемые потоки доводятся до безопасной точки и останавливаются;
- маркировка: от корней (локальные переменные на стеке, регистры, статические поля,
GCHandle, очередь f-reachable) обходится граф ссылок, всё достижимое помечается живым. При сборке молодого поколения ссылки из старых объектов берутся из card table, чтобы не сканировать Gen 2 целиком; - финализируемые объекты: недостижимый объект с финализатором не удаляется, а попадает в очередь f-reachable. Финализатор выполнит отдельный поток, а память освободится только на следующей сборке;
- компактизация: живые объекты сдвигаются друг к другу, ссылки на них переписываются, выжившие переходят в следующее поколение. Если двигать невыгодно, сборщик делает sweep и заносит пустоты в список свободных блоков;
- возобновление потоков.
Объекты от 85 000 байт попадают в LOH, её собирают вместе с Gen 2 и по умолчанию не компактизируют. Режимы задаются отдельно: workstation или server GC, фоновая сборка Gen 2 (System.GC.Concurrent), GCSettings.LatencyMode для участков, чувствительных к паузам.
Где спотыкаются
Говорят, что .NET считает ссылки. Счётчиков в CLR нет, поэтому циклические ссылки для сборщика не проблема.
Называют Gen 0, Gen 1 и Gen 2 режимами сборщика. Это поколения кучи, а режимы это workstation, server и фоновая сборка.
Путают Dispose и сборку мусора. Dispose освобождает неуправляемые ресурсы по вызову и управляемую память не трогает, а финализатор без GC.SuppressFinalize задерживает объект минимум на одну лишнюю сборку.