В жизни каждого разработчика наступает момент, когда хочется разобраться, как что-то устроено изнутри. Уже не столько с практической точки зрения. А сколько становится интересным само по себе. Как это работает? А как работает под капотом?
(О основах работы сборщика мусора можно почитать самостоятельно, благо инфы много).
Какой GC использует Unity?
Многие думают, что тот самый, который например описан в CLR via C#.
На самом деле нет!
В юнити в качестве GC используется Boehm garbage collector (Boehm GC), исходный код которого можно найти на GitHub.
Hans Boehm описывает работу этого GC следующим образом:
Коллектор использует алгоритм mark-sweep. Он обеспечивает инкрементальную и поколенческую сборку мусора в операционных системах, которые обеспечивают правильную поддержку виртуальной памяти
В любой реализации GC существует понятие «достижимый» объект. Если объект достижимый, значит на него кто-то ссылается.
Если же он не достижимый, то на него никто не ссылается, и он является претендентом на удаление.
Из описания Hans Boehm понимаем, что нужно узнать, что такое mark-sweep (отметить — подмести).
Он описан например в замечательной книге дракона:
Изначально все корневые объекты добавляются как не сканированные.
— Далее первым циклом проходимся по всем дочерним элементам каждого корня. Если элемент является недостижимым, то помечаем.
— Вторым циклом очищаем все помеченные элементы.
Есть интересные модификации, советую ознакомиться.
Boehm GC — консервативный сборщик мусора. Это значит, ему не требуется точная информация о расположении всех указателей объектов в памяти. Вместо этого он предполагает, что любое значение в памяти, которое может быть указателем на объект, является допустимым указателем.
Концептуально он работает примерно в 4 этапа:
- Подготовка. Каждый объект имеет связанный бит метки. GC чистит все биты метки. Указывает, что все объекты потенциально недоступны. (Unreached).
- Фаза Маркировки. Отмечает все объекты, к которым можно получить доступ через цепочки указателей на переменные. Часто сборщик не имеет реальной информации о расположении переменных-указателей в куче, поэтому он рассматривает все области статических данных, стеки и регистры как потенциально содержащие указатели. Любые битовые комбинации, представляющие адреса внутри объектов кучи, управляемых сборщиком, рассматриваются как указатели...
- Фаза Очистки. Сканирует кучу на наличие недоступных и, следовательно, немаркированных объектов и возвращает их в соответствующий свободный список для повторного использования. На самом деле это не отдельный этап; даже в неинкрементном режиме эта операция обычно выполняется по требованию во время выделения, при котором обнаруживается пустой список свободных мест. Таким образом, маловероятно, что фаза очистки затронет страницу, которая в любом случае не была бы затронута вскоре после этого.
- Фаза Финализации. Недоступные объекты, которые были зарегистрированы для финализации, ставятся в очередь для финализации вне сборщика.
Из сайта BDW - Hans Boehm
Можно заметить, что модификация в алгоритме напоминает одно из улучшений mark-sweep с помощью алгоритма Бейкера.
В книге дракона он описан примерно так:
- Изначально все объекты помечаются как недостижимые
- Проходимся от корней и ставим как достижимые все объекты, до которых мы добрались
- Оставшиеся недостижимые объекты — очищаются, помечаются свободными.
Все просто.
— Обратите внимание, в фазе маркировки Boehm GC отмечает все объекты, до которых может добраться из переменных-указателей (pointer variables). Поскольку обычно он не может определить, где расположены переменные-указатели, он сканирует следующие корневые сегменты на наличие указателей:
1. Регистры
2. Стек
3. Статические области памяти
Это только первая часть!
В следующей мы разберем инкрементную и поколенческую часть Boehm GC.
Подписывайтесь на telegram канал, чтобы не пропустить следующие части!👊