Профиль heap показывает высокий alloc_space, но небольшой inuse_space. Какой вывод о жизненном цикле объектов следует сделать?
Высокий alloc_space при небольшом inuse_space означает, что приложение накопило большой объём аллокаций, но большинство созданных объектов уже недостижимо и освобождено сборщиком мусора. Это признак высокой текучести объектов, а не обязательно утечки памяти.
Если одновременно растут задержки или загрузка CPU, причиной может быть стоимость самих аллокаций, обнуления памяти, write barrier и работы GC. Небольшой inuse_space показывает только текущий объём памяти, удерживаемый живыми объектами, и не отменяет затрат на частое создание временных значений.
Профилирование памяти должно было отвечать на две разные задачи: найти объекты, которые удерживаются слишком долго, и обнаружить места, создающие чрезмерный поток временных объектов. Один показатель не может корректно описать обе проблемы.
Поэтому в инструментах профилирования Go разделяются метрики текущего использования памяти и накопленного объёма аллокаций. Такой подход позволяет отличить утечку или избыточное удержание объектов от высокой интенсивности их создания.
Предположим, обработчик запросов создаёт много временных структур. После каждого запроса они становятся недостижимыми, поэтому объём живой кучи остаётся почти постоянным. Однако GC вынужден регулярно просматривать и освобождать эти объекты, а аллокатор — постоянно выдавать новую память.
Если смотреть только на inuse_space, источник нагрузки можно не заметить: профиль покажет небольшой текущий объём памяти. Ошибочный вывод о наличии утечки приведёт к попытке уменьшать долгоживущие структуры, хотя реальная проблема заключается в избыточной текучести объектов.
inuse_space оценивает объём памяти, связанный с объектами, которые считаются живыми в момент построения heap-профиля. Он полезен для поиска удерживаемых структур: кэшей, больших графов объектов, неограниченных очередей и случайно сохранённых ссылок.
alloc_space отражает суммарный объём памяти, выделенный за период, включая объекты, которые впоследствии были освобождены. Поэтому высокий показатель означает интенсивный поток аллокаций. Аналогично, inuse_objects описывает число живых объектов, а alloc_objects — накопленное число созданных объектов.
Профили heap в Go основаны на выборочном учёте аллокаций, поэтому значения являются оценками, а не бухгалтерским журналом каждой операции. При сравнении версий приложения важно сохранять сопоставимые условия нагрузки и настройки профилирования.
Интерпретация обычно выглядит так:
alloc_objects при небольшом alloc_space — создаётся много мелких объектов.Профиль не доказывает причинность сам по себе. После нахождения горячего места нужно сопоставить его с CPU-профилем, частотой GC, задержками и трассировкой. Оптимизация может состоять в уменьшении числа временных значений, переиспользовании памяти, изменении формата данных или устранении лишнего копирования, но использование пулов оправдано только после проверки их влияния на сложность и удержание памяти.
В сервисе обработки сообщений alloc_space был значительно выше inuse_space. Основной вклад давала функция преобразования входных данных: она создавала временные строки, срезы и небольшие структуры, которые жили только до конца обработки сообщения.
Рассматривались три варианта. Увеличение GOGC уменьшало частоту циклов GC, но повышало допустимый размер кучи и задерживало возврат памяти. sync.Pool снижал число повторных аллокаций, но усложнял владение буферами и не гарантировал их сохранение между циклами GC. Переписывание преобразования с использованием заранее выделяемого рабочего буфера уменьшало аллокации без глобального кэша объектов, но требовало аккуратно контролировать границы времени жизни данных.
Выбрали локальное переиспользование буфера и устранение лишних преобразований. После этого уменьшился alloc_space, а inuse_space почти не изменился, что подтвердило исходную гипотезу: проблемой была текучесть объектов, а не утечка. Параллельно снизились частота GC и процессорные затраты на обработку сообщений.
alloc_space сам по себе доказывать утечку памяти?Нет. Он показывает накопленный объём аллокаций, включая уже освобождённые объекты. Для подозрения на утечку важнее устойчивый рост inuse_space после одинаковых этапов нагрузки и проверка цепочек удерживающих ссылок.
inuse_space не всегда означает, что приложение плохо расходует память?Живая память может быть оправданной: её могут занимать рабочие кэши, индексы или буферы, ограниченные понятным размером. Нужно выяснить, растёт ли этот объём без ограничения и действительно ли он нужен. Само наличие большого живого набора не является доказательством ошибки.
alloc_space между двумя профилями может быть некорректным?На результат влияют длительность и интенсивность нагрузки, момент снятия профиля, выборочное профилирование и параметры запуска. Сравнивать следует сопоставимые сценарии, а лучше дополнительно нормировать показатель на число запросов или сообщений. Иначе более длинный тест может выглядеть хуже только из-за большего времени накопления статистики.