Сборка мусора во Flutter

от автора

В РСХБ мобильную разработку мы ведём преимущественно на Flutter. В новой статье поговорим об алгоритме, который используется в любом приложении – о сборщике мусора (англ. Garbage collection). Его основной задачей, как в любом высокоуровневом языке программирования, является наблюдение за ссылками и очистка областей памяти с целью предотвращения переполнения памяти. Под катом рассказываем о том, как во Flutter работает процесс сборки “мусора” – неактивных и неиспользуемых ссылок, а также локальных и глобальных экземпляров языка Dart.

В языке Dart применяется сборщик мусора по поколениям объектов. Сборщик мусора для сборки экземпляров с коротким жизненным циклом и сборщик мусора для долгоживущих экземпляров, переживших более одного цикла сборки мусора.

В процессе сборки мусора слой Framework Dart создает канал взаимодействия со слоем Flutter Engine, посредством которого узнает о моментах простоя приложения и отсутствия пользовательского взаимодействия. В эти моменты Framework Dart запускает процесс оптимизации памяти, что позволяет сократить влияния на пользовательский опыт и стабильность приложения. Кроме того, в процессе работы приложения используется скользящее сжатие, что сводит к минимуму нагрузку на память за счёт уменьшения фрагментации памяти.

Сборщик молодого мусора

Алгоритм сборщика молодого мусора выполняет задачу очистки объектов с коротким жизненным циклом. Этот алгоритм схож по своей структуре с алгоритмом, используемым в JavaScript. Основная идей сборщика мусора молодого поколения заключается в том, что обрабатываются не все находящиеся в памяти объекты, а только объекты молодого поколения. Сборщик этого типа вызывается значительно чаще и работает значительно быстрее, чем сборщик старшего поколения.

Процесс работы

Используемый объём памяти можно условно разделить на два полупространства: одно всегда активно, другое неактивно. Новые объекты располагаются в активной части, где по мере её заполнения, живые объекты переносятся из активной области памяти в неактивную, игнорируя мёртвые объекты. Затем неактивная половина становится активной – процесс имеет цикличный характер.

Для категорирования объектов на живые и мёртвые, сборщик мусора анализирует корневые объекты стека и проверяет, на что они ссылаются. Следует отметить, что в языке Dart не используются настоящие указатели на объекты, как это делается в C/C++, вместо них фреймворк просто копирует ссылки на экземпляры. Живыми объектами считаются объекты, до которых существует путь по ссылке от корневого объекта. Следовательно, объекты, не имеющие такого пути, помечаются как мёртвые. Затем сборщик перемещает живые объекты в неактивную область памяти. Процесс продолжается до тех пор, пока все объекты, подходящие под определение живых, не будут перемещены. Мёртвые объекты сборщик не копирует: в дальнейшем мёртвые объекты будут перезаписаны живыми.

Сборщик мусора “Parallel Marking and Concurrent Sweeping”

В процессе выполнения сборщика молодого мусора память наполняется “долгоживущими” объектами. Для обработки таких объектов используется алгоритм “Parallel Marking and Concurrent Sweeping”.

Алгоритм состоит из этапов: маркировку живых и удаление мёртвых объектов. На первом этапе осуществляется обход дерева объектов, используемые объекты помечаются специальной меткой. Во время второго этапа происходит повторный проход по дереву объектов, в ходе которого непомеченные в первом этапе объекты перерабатываются. Затем все метки стираются. Сборщик мусора блокирует объекты памяти на изменение, следовательно, и пользовательский интерфейс. Блокировка осуществляет для того, чтобы избежать изменение статусов объектов, находящихся в памяти. После выполнения процедуры маркировки объектов, приложение возобновит работу запустит процесс очистки мёртвых объектов. Процесс маркировки и очистки выполняются параллельно с работой приложения.

Работа сборщика мусора в изолятах

По умолчанию программа на Dart выполняется в единственном изоляте (потоке), следовательно, и сборщик мусора является единственным. При создании дополнительного изолята выделяется отдельная автономная область памяти, которая имеет собственный сборщик. Использование изолятов позволяет разгрузить сборщики мусора главного изолята при работе с высоконагруженными процессами. Данный подход является ярким представителем хорошей практики использования особенностей языка Dart.

Const & final

Неотъемлемым атрибутом работы Flutter разработчика является использование ключевых слов const и final. Ключевые слова const и final, на первый взгляд, имеют одинаковое значение, но на самом деле это не так. С точки зрения памяти, ключевое const означает, что константно не только значение, но и сам экземпляр, при использовании final константно только значение экземпляра. Следовательно, при использовании экземпляра final в памяти выделяется новая область памяти, даже если значение объекта будет идентично. При использовании const переменной новая область памяти не выделяется, а используется ссылка на уже существующий экземпляр. Таким образом, сборщику мусора не придется обрабатывать множество идентичных объектов, сборщику мусора необходимо будет проверить только состояние ссылок на этот экземпляр. Грамотное использование ключевых слов const и final повысит эффективность выполнения процесса сборки мусора, следовательно, приложения в целом.

Выводы

В процессе рассмотрения алгоритма сборки мусора в Dart складывается мнение, что периодические блокировки памяти только тормозят работу приложение, но это не так. Жизненный цикл приложения Dart устроен таким образом, что сборка мусора осуществляется в моменты технических пауз среды выполнения языка Dart. Учитывая способность Flutter регулировать свои технические процессы, влияние на пользовательский опыт в процессе сборки мусора сводится к минимуму.

Разработчики языка Dart провели отличную работу по созданию средств управления памятью, результатом которой стал мощный инструмент сборки мусора, обеспечивающий высокое быстродействие.


ссылка на оригинал статьи https://habr.com/ru/company/rshb/blog/668600/