Добавить объявление

Как работает сборщик мусора в Dart. Память, указатели и smi

Сборщик мусора в Dart - тема, которая часто всплывает на собеседованиях, но найти внятное объяснение непросто. Статьи либо слишком сложные, либо уходят в дебри реализации VM. Давайте разберем основы: как Dart управляет памятью, что такое указатели и почему числа не влияют на производительность.

Биты, байты и указатели:


Память компьютера - это длинный ряд ячеек, каждая из которых содержит бит (0 или 1). Процессоры работают с группами битов. В 64-битных процессорах это группы по 64 бита (8 байт). Это машинное слово.

Указатель в Dart занимает ровно одно машинное слово - 8 байт.

Указатель - это не сам адрес, а место, где этот адрес записан. Если представить адрес дома, то листок бумаги - это указатель, а то, что на нем написано - сам адрес.

Как Dart отличает число от объекта:


Dart числа и объекты хранятся по-разному. Если переменная содержит число, оно хранится прямо в указателе - никакого перехода по адресу не требуется. Если переменная содержит объект, в указателе хранится адрес этого объекта, и чтобы получить доступ к объекту, нужно перейти по этому адресу.

Как Dart понимает, что именно лежит в указателе? Все решает последний бит.

Адреса объектов в памяти кратны 16. Это сделано специально. Если адрес кратен 16, его последние биты всегда нули. Вот это пустующее место Dart использует как флажок.

Последний бит говорит, чем является значение в указателе:

  • 1 - ссылка на объект

  • 0 - число, искать объект по адресу не нужно

Пример: объект лежит по адресу 0x00A03F50. Этот адрес кратен 16. В двоичном виде последний байт: 0101 0000. Dart ставит единицу в конце: 0101 0001 -> 0x00A03F51. Теперь это помеченный адрес объекта.

Чтобы дойти до реального объекта, нужно убрать эту единицу и получить исходный адрес.

Smi: числа, которые живут в указателе:


Если число умещается в указателе, оно называется smi (small integer). Smi не занимает места в куче. Оно живет прямо в указателе. Сборщик мусора за ним не следит.

Пример: число 7 = 111 в битах. Когда Dart упаковывает его в smi, он сдвигает биты влево на 1 позицию. Получается 1110. Ноль на конце означает, что это число, а не ссылка. При распаковке число делится на 2, возвращая исходное значение.

Handles - почему C++ не теряет объекты:


Сборщик мусора периодически двигает объекты в памяти, чтобы бороться с фрагментацией. Объект переезжает на новый адрес. Для Dart-кода это не проблема - сборщик знает все ссылки и обновляет их.

Но есть код на C++ (движок, нативные библиотеки). Сборщик не знает, где у C++ лежат ссылки. Если объект переедет, C++ останется со старым адресом - программа упадет.

Решение: handles. Это ссылка на ссылку. C++ держит не объект, а handle с адресом объекта. Когда объект переезжает, сборщик обновляет адрес внутри handle. C++ всегда смотрит на актуальный адрес через handle.

Что из этого важно на практике:


Числа практически ничего не стоят с точки зрения производительности. Индексы, счетчики, любые небольшие значения - все это smi. Они не выделяются в куче, не создают мусора и не нагружают сборщик. Можно смело создавать сотни тысяч таких чисел без последствий.

А вот объекты - это совсем другая история. Каждый новый объект попадает в кучу, и сборщик вынужден за ним следить. Чем больше лишних объектов создается в коде, тем чаще сборщику приходится просыпаться, чтобы убрать мусор. И тем больше времени уходит на паузы, которые пользователь может заметить.

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

Вывод:


Dart управляет памятью хитро, но логично. Указатели хранят либо числа (smi), либо ссылки на объекты. Различие определяется последним битом. Сборщик следит за объектами в куче, но не трогает числа. Handles решают проблему с C++ ссылками при перемещении объектов.

На практике это значит: числа не влияют на производительность, объекты - да. Создание лишних объектов напрямую влияет на нагрузку сборщика. Понимание этих основ помогает писать более эффективный код.
04.08.2026 41 612