Как Dart оптимизирует очистку памяти?

«Как Dart оптимизирует очистку памяти?» — вопрос из категории Dart Core, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Dart использует продвинутый сборщик мусора (Garbage Collector), который сочетает несколько стратегий для минимизации пауз и эффективного управления памятью.

Основные механизмы:

  1. Generational Garbage Collection (Поколенческий GC)

    • Память разделена на молодое (new space) и старое (old space) поколения.
    • Большинство объектов умирают вскоре после создания. GC часто и быстро сканирует молодое поколение, освобождая кратковременные объекты.
    • Объекты, пережившие несколько сборок в молодом поколении, продвигаются в старое, которое сканируется реже.
  2. Incremental & Concurrent Marking (Инкрементальная и конкурентная маркировка)

    • Фаза маркировки (определения живых объектов) выполняется небольшими порциями, чередуясь с выполнением кода приложения, что предотвращает заметные "фризы".
    • Маркировка может выполняться параллельно в отдельном потоке (concurrent marking).
  3. Compacting (Компактизация)

    • После освобождения памяти GC может перемещать оставшиеся объекты, устраняя фрагментацию и ускоряя последующие выделения памяти.

Пример и практические советы:

// Пример циклической ссылки, которая будет корректно обработана современным GC
class Node {
  Node? next;
}

void main() {
  var a = Node();
  var b = Node();
  a.next = b;
  b.next = a; // Циклическая ссылка
  // Современный GC (с подсчётом ссылок или через трёхцветную маркировку) обнаружит,
  // что на эту группу объектов нет внешних ссылок, и удалит их.
}

Для оптимизации использования памяти в Flutter-приложениях я следую таким практикам:

  • Своевременно отписываюсь: Всегда вызываю cancel() для StreamSubscription, Timer и слушателей ChangeNotifier/ValueNotifier в методах dispose() виджетов.
  • Избегаю удержания больших объектов в глобальном контексте без необходимости.
  • Использую const-конструкторы для виджетов и объектов, которые не меняются, чтобы уменьшить нагрузку на GC.
  • Для кеширования рассматриваю WeakReference из пакета dart:weak для объектов, которые можно безопасно пересоздать, если память понадобится системе.