open atlas
↑ К треку
Производительность PERF · 04 · 01

Основы GC: за что рантайм берёт налог

Сборщик мусора автоматически освобождает память в куче — но работа проявляется как паузы и CPU. Именно rate аллокаций, а не размер кучи, определяет хвостовую latency.

PERF Junior ◷ 12 min
Уровень
ОсновыJuniorMiddleSenior
Уже знаешь этот юнит? Пройди быструю проверку за минуту →

p99 latency сервиса взлетает до 800 мс каждые несколько секунд. CPU в норме. Медленных запросов нет. Открываем GC-лог — паузы 600-700 мс каждые 4 секунды. Сервис аллоцирует 1 ГБ/сек, и рантайм останавливает весь мир для уборки.

Что делает сборщик мусора

Сборщик мусора обменивает CPU на memory safety: обходит кучу, маркирует всё достижимое от корней (регистры, стеки потоков, глобальные переменные) и возвращает остальное. Вам не нужно вызывать free() — рантайм делает это за вас. Цена: рантайм тратит CPU на учёт, и иногда останавливает приложение для этого.

Три рычага всегда в напряжении:

  • Время паузы — как долго приложение остановлено, пока GC работает.
  • Throughput — сколько CPU коллектор забирает у вашего кода.
  • Размер кучи — сколько overhead-памяти нужно коллектору для эффективной работы.

Минимизировать все три одновременно невозможно.

Метафора кухни

Представьте кухню ресторана. Повара (ваш код) производят грязную посуду (аллокации). Посудомойщик (GC) её чистит. Если повара генерят посуду быстрее, чем мойщик успевает, посуда копится — кухне приходится останавливаться, чтобы разгрузить раковину. Современный concurrent-посудомойщик моет, пока повара работают; старый stop-the-world-посудомойщик заставляет всех ждать. Даже лучший посудомойщик стоит воды и электричества (CPU). Быстрая кухня — не от магического посудомойщика: от поваров, переиспользующих тарелки.

Почему rate аллокаций важнее размера кучи

Размер кучи показывает, сколько памяти программа держит в данный момент. Rate аллокаций показывает, как часто запрашивается новая память. Циклы GC запускаются пропорционально скорости накопления мусора — то есть rate аллокаций, а не размер кучи.

СценарийРазмер кучиRate аллокацийЧастота пауз GC
Большой стабильный кэш4 ГБ50 МБ/сРаз в ~80 с, короткие паузы
High-throughput API100 МБ1 ГБ/сКаждые ~0,1 с, частые спайки

Сервис с 4 ГБ кучи, но медленными аллокациями, почти не замечает пауз. Сервис со 100 МБ кучи и rate 1 ГБ/с — в постоянном GC. Рычаг для хвостовой latency — rate аллокаций, а не размер кучи.

Цикл mark-sweep

Любой tracing GC следует одной схеме:

  1. Сканирование корней — кратко останавливаем, определяем корни (регистры, стеки, глобалы).
  2. Маркировка — обходим граф ссылок от корней; маркируем каждый достижимый объект.
  3. Очистка — возвращаем память каждого немаркированного объекта.
  4. Опционально компактизация — перемещаем живые объекты, устраняя фрагментацию.
  5. Обновление ссылок — исправляем указатели на перемещённые объекты.
  6. Возобновление — приложение работает на полной скорости до следующего цикла.

Все шесть образуют один полный цикл GC; без сканирования корней коллектор не знает, что живо, и все последующие шаги теряют смысл.

Наивная версия паузит приложение на шагах 1–5. Для кучи 32 ГБ это могут быть секунды — неприемлемо для latency-sensitive сервиса. Современные коллекторы сокращают или устраняют большинство STW-фаз.

Concurrent vs stop-the-world GC

Stop-the-world (STW) коллектор паузит все потоки приложения, пока работает. Простой в реализации; паузы растут с размером кучи.

Concurrent коллектор выполняет большую часть работы параллельно с потоками приложения, поэтому видимые пользователю паузы коротки (субмиллисекундные) вместо длинных (десятки-сотни мс). Ему нужны write barriers (барьеры записи — небольшие инструкции, вставляемые компилятором при каждом обновлении ссылки) — они держат коллектор в курсе изменений, пока приложение работает. Стоимость барьера ~2-10% CPU; выгода — короткие паузы.

Почему это работает

Все современные concurrent GC всё ещё имеют некоторые stop-the-world фазы — сканирование корней, обработка weak-ref, remap. Concurrent коллекторы минимизируют STW, но не устраняют его. Всегда смотрите GC-логи на реальное распределение пауз, а не на заголовки от вендоров.

Викторина

p99 latency сервиса коррелирует с паузами GC. Что проверить ПЕРВЫМ?

Викторина

Почему concurrent GC предпочтительнее stop-the-world GC для production-сервисов?

Расставь шаги по порядку

Расставьте концептуальные стадии типичного цикла сборки мусора по порядку:

  1. 1 Определить корни — регистры, стеки потоков, глобальные переменные
  2. 2 Маркировка — обход от корней, пометка каждого достижимого объекта
  3. 3 Очистка — возврат памяти немаркированных объектов
  4. 4 Опционально компактизация — перемещение живых объектов для устранения фрагментации
  5. 5 Обновление ссылок на перемещённые объекты
  6. 6 Возобновление приложения на полной скорости до следующего цикла
Закончи аналогию

Заполните пропуск: сборка мусора обменивает CPU на memory _______ — вам не нужно отслеживать каждую аллокацию вручную, но рантайм платит за учёт циклами, которые могли бы достаться вашему коду.

Вспомните перед уходом
  1. 01
    В одном абзаце: почему rate аллокаций важнее общего размера кучи для хвостовой latency, определяемой GC?
  2. 02
    Назовите 3-way tradeoff любого GC и приведите пример коллектора, оптимизирующего каждый из крайних значений.
Итог

Сборщик мусора маркирует достижимые объекты и возвращает остальное, обменивая циклы CPU на memory safety. Три рычага — время паузы, throughput и размер кучи — нельзя минимизировать одновременно. Rate аллокаций определяет частоту циклов GC сильнее, чем размер кучи: небольшой сервис с rate 1 ГБ/с испытывает куда большее давление GC, чем большой кэш с rate 50 МБ/с. Современные concurrent GC делают большую часть работы параллельно с потоками приложения, удерживая паузы ниже 1 мс, но всё ещё имеют короткие STW-фазы для сканирования корней. Первый рычаг для хвостовой latency от GC — всегда профиль аллокаций, а не выбор коллектора. Теперь, когда видишь p99-спайк при нормальном CPU и без медленных запросов — открывай GC-лог прежде всего остального.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 5 завершено
Связанные уроки
встречается в162

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

Примени это

Примени этот урок в реальном проекте.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.