Как работает сборщик мусора (Garbage Collection) в .NET?

«Как работает сборщик мусора (Garbage Collection) в .NET?» — вопрос из категории Управление памятью, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Сборка мусора (GC) в .NET — это высокооптимизированный механизм автоматического управления памятью для управляемой кучи (managed heap). Его основная цель — освобождать память, занятую объектами, которые больше не достижимы из корней приложения.

Основные принципы:

  1. Поколения (Generations): Куча разделена на три поколения (Gen 0, Gen 1, Gen 2) для оптимизации.

    • Gen 0: Молодые, недавно созданные объекты. Сборка здесь происходит часто и быстро, так как большинство объектов быстро становятся недостижимыми.
    • Gen 1: Объекты, пережившие одну сборку Gen 0. Служит буфером между Gen 0 и Gen 2.
    • Gen 2: Долгоживущие объекты (например, статические данные, кэши). Сборка здесь наиболее затратна.
    • Куча больших объектов (LOH): Отдельная область для объектов > 85 КБ. Собирается вместе с Gen 2, но без компрессии по умолчанию.
  2. Фазы сборки мусора:

    • Пометка (Marking): GC начинает с корневых ссылок (статические поля, локальные переменные, регистры CPU) и строит граф всех достижимых объектов.
    • Перемещение/Сжатие (Relocation/Compaction): (Для Gen 0/1) Выжившие объекты перемещаются в старшее поколение, освобождая непрерывный блок памяти. Указатели обновляются.
    • Освобождение (Sweeping): Память, занятая непомеченными (недостижимыми) объектами, освобождается.

Практическое влияние на код:

  • Краткосрочные объекты дешевы. Не бойтесь создавать их в методах — они будут собраны в быстрой сборке Gen 0.
  • Долгоживущие объекты дороги. Избегайте необоснованного подъема объектов в Gen 2 (например, кэши без политики очистки).
  • Реализуйте IDisposable для освобождения неуправляемых ресурсов (файлы, сокеты, дескрипторы). GC не управляет такой памятью.
    // Правильное освобождение неуправляемых ресурсов
    using (var fileStream = new FileStream("file.txt", FileMode.Open))
    using (var reader = new StreamReader(fileStream))
    {
        var content = reader.ReadToEnd();
    } // Dispose() будет вызван автоматически, даже при исключении
  • Избегайте вызовов GC.Collect() в рабочем коде. Это нарушает внутреннюю эвристику GC и обычно ухудшает производительность. Оставьте решение сборщику.

Вывод: GC в .NET — сложный и эффективный механизм. Понимание его работы помогает писать производительный код, избегая утечек памяти и излишней фрагментации.