Что позволяет сборщику мусора Go отличать занятые слоты size-класса в span от уже освобождённых?
Сборщик мусора Go использует метаданные span, прежде всего битовые карты занятости и маркировки. Карта занятости показывает, какие слоты действительно содержат выделенные объекты, а карта маркировки — какие из них достижимы в текущем цикле GC. Поэтому свободные слоты не сканируются как объекты и не увеличивают объём работы маркировки.
Аллокатор Go размещает небольшие объекты в блоках памяти — span, разделённых на слоты одного size-класса. Такой подход позволяет быстро переиспользовать освобождённые участки и уменьшает накладные расходы по сравнению с отдельным получением памяти под каждый объект.
GC при этом должен отличать реальный объект от свободного слота. Без такой информации сборщик либо сканировал бы несуществующие объекты, либо рисковал бы ошибочно учитывать свободную память как занятую.
Представим span на 64 слота, из которых заняты только 10. Размер span сам по себе не говорит, какие позиции содержат объекты, а какие уже доступны аллокатору.
Если GC будет проверять все слоты одинаково, он потратит лишнее время на просмотр свободной памяти. Если же он неверно сочтёт свободный слот объектом, это может привести к ошибочному поиску указателей и удержанию недостижимых объектов.
У span есть информация о состоянии каждого слота. Allocation bitmap отражает, выделен ли слот объекту. Во время сборки мусора для выделенных объектов используются отдельные данные маркировки: помечен слот или нет.
Во время маркировки GC сначала работает только с занятыми объектами. Для типа, содержащего указатели, он сканирует соответствующий объект и может пометить найденные объекты. Свободные слоты не являются частью графа объектов и в этот граф не попадают.
На этапе sweep недостижимые выделенные слоты освобождаются: их состояние меняется так, чтобы аллокатор мог повторно использовать их. Свободные слоты не нужно дополнительно освобождать сборщику — они уже считаются доступными для размещения.
Для noscan-объектов, не содержащих указателей, GC не сканирует содержимое даже если слот занят. Это уменьшает стоимость маркировки, но не отменяет учёт занятости слота. Для крупных объектов, размещаемых отдельно от обычных size-классов, механизм представления занятости другой, однако общий принцип тот же: сборщик обрабатывает реальные выделенные объекты, а не весь резерв памяти подряд.
Практическое следствие: большое количество свободных слотов внутри уже полученных span не обязательно увеличивает работу маркировки, но может влиять на расход памяти процесса и на работу аллокатора. Высокая стоимость может появиться при большом числе переходов объектов между состояниями, при sweep и при необходимости получать новые span.
В сервисе часто создаются и освобождаются небольшие структуры размером 64 байта. В профиле видно, что span содержит много свободных слотов, но время маркировки GC остаётся почти неизменным.
Вариант с принудительным уменьшением числа span может сократить резерв памяти, но обычно добавляет давление на аллокатор и обращения к общей куче. Вариант с sync.Pool уменьшает число повторных аллокаций, однако объекты в пуле не следует считать гарантированно сохранёнными между циклами GC.
Рациональное решение — сначала проверить профиль аллокаций и размер живой кучи, затем использовать повторное применение объектов только там, где это действительно снижает стоимость аллокаций. Результат оценивают по задержкам, CPU, живой куче и RSS, а не по числу свободных слотов в одном span.
Вопрос: Свободный слот в span считается объектом без указателей?
Ответ: Нет. Свободный слот не является выделенным объектом и не должен сканироваться. Даже если его прежнее содержимое физически ещё не затёрто, эти байты не участвуют в графе достижимости.
Вопрос: Достаточно ли карты занятости, чтобы понять, какие объекты нужно оставить после GC?
Ответ: Нет. Карта занятости отвечает на вопрос «существует ли здесь выделенный объект», но не на вопрос «достижим ли он». Для второго используется информация маркировки, полученная обходом корней и ссылок между объектами.
Вопрос: Уменьшится ли время GC пропорционально числу свободных слотов в span?
Ответ: Нет. Свободные слоты сами по себе не сканируются, поэтому их появление может почти не изменить стоимость маркировки. На общее время влияют число живых указательных объектов, объём сканируемой памяти, корни, барьеры записи, sweep и работа аллокатора. Освобождение слотов может уменьшить будущие аллокационные расходы, но не гарантирует немедленного уменьшения RSS процесса.