Сборщик мусора в .NET
Одним из самых популярных на собеседованиях C# разработчиков является вопрос о работе сборщика мусора в .NET. Мне также приходилось рассказывать о нём интервьюерам на моих собесах. Сегодня я хотел бы кратко рассказать о сборщике мусора, заодно и сам вспомню.
🔍 Зачем нужен сборщик мусора?
Сборщик мусора автоматически обеспечивает освобождение памяти, занятой объектами, которые больше не используются. Такой подход предотвращает утечки памяти и снимает с разработчика бремя ручного освобождения памяти (привет С++), что позволяет сосредотачиваться на более важных аспектах программирования.
⏰ Когда вызывается сборщик мусора?
Сборщик мусора активируется автоматически, например, когда одно из поколений заполнено (о поколениях позже). Если вам требуется инициировать сборку мусора вручную, используйте GC.Collect().
🔗 Как определяется, какие объекты больше не используются?
Сборщик мусора опирается на концепцию "GC Roots" (корни сборки мусора):
1. Локальные переменные: Объекты, на которые существуют ссылки в локальных переменных.
2. Статические переменные: Объекты, на которые ссылаются статические переменные классов. Эти корни живут в течение всего времени выполнения приложения.
3. Активные элементы стека: Если во время выполнения функции происходит сборка мусора, то локальная переменная внутри данной функции не будет удалена сборщиком мусора. Такая переменная считается активным корнем, до тех пор пока кадр стека метода не будет разрушен.
Сборщик мусора создаёт граф (фаза маркировки), который содержит все объекты, достижимые из GC Roots. Объекты, на которые нет ссылок из GC Roots, считаются недостижимыми и готовыми для удаления.
🔄 Как работает сборщик мусора?
Сборщик мусора использует концепцию поколений (поколения 0, 1 и 2) для эффективной работы с объектами различной "старости".
- При создании объекты помещаются в поколение 0. Если объект слишком большой (по умолчанию объекты размером больше 85 000 байт), то он будет помещён в Large Object Heap (очищается вместе с поколением 2).
- Когда поколение 0 заполнено — запускается сборка мусора. Неиспользуемые объекты удаляются (недостижимые из GC Roots), оставшиеся перемещаются в поколение 1.
- Аналогично происходит сборка мусора, когда заполнено поколение 1. Все выжившие объекты перемещаются в поколение 2. Затем происходит сборка мусора в поколении 0.
- Когда заполнено поколение 2, происходит полная сборка мусора. Сперва очищается поколение 2, а затем 1 и 0. Если после этого недостаточно места для новых объектов — происходит исключение OutOfMemory.
- В самом конце происходит фаза сжатия, в которой сборщик мусора перемещает живые объекты так, чтобы они располагались в памяти непосредственно друг за другом.
🔄 Для чего нужно разделение на несколько поколений?
- Сборка мусора поколения 0 выполняется чаще и занимается удалением объектов, которые быстро выходят из области видимости.
- С увеличением поколения редкость запуска сборки позволяет более эффективно использовать ресурсы. Поколение 0 очищается чаще всего, поколение 2 очищается реже всего.
🌐 Ещё по теме:
- .NET Memory Management Concepts — очень кратко об основных концепциях сборщика мусора
- Поколения объектов — одна из очень подробного цикла статей о сборщике мусора