openjdk.ruOpenJDK на русском

JEP 404: Generational Shenandoah (Experimental)

Generational Shenandoah (Shenandoah с поколениями) в статусе Experimental (экспериментальная функция)

AuthorsBernd Mathiske, Kelvin Nilsen, William Kemper, and Ramki Ramakrishna
ОтветственныйWilliam Kemper
ТипFeature
ОбластьImplementation
СтатусClosed / Delivered
Выпуск24
Компонентhotspot / gc
Обсуждениеhotspot dash gc dash dev at openjdk dot java dot net
ТрудоёмкостьL
ДлительностьL
Связан сJEP 521: Generational Shenandoah
JEP 535: Shenandoah GC: Generational Mode by Default
РецензентыAleksey Shipilev, Roman Kennke
ОдобренVladimir Kozlov
Создан2021/02/01 22:49
Обновлён2026/03/10 21:41
Задача8260865

Аннотация

Дополнить сборщик мусора Shenandoah возможностями сборки мусора с поколениями в статусе Experimental, чтобы повысить устойчивую пропускную способность, устойчивость к всплескам нагрузки и эффективность использования памяти.

Цели

Главная цель — предоставить Generational Mode (режим с поколениями) в статусе Experimental, не нарушая работу Shenandoah без поколений. Мы намерены сделать Generational Mode режимом по умолчанию в одном из будущих выпусков.

Остальные цели сформулированы относительно Shenandoah без поколений:

  • Уменьшить устойчивый объём занимаемой памяти, не жертвуя короткими паузами GC.

  • Снизить потребление процессорного времени и энергии.

  • Снизить риск вырожденных и полных сборок при всплесках выделения памяти.

  • Сохранять высокую пропускную способность.

  • Сохранить поддержку сжатых указателей на объекты.

  • На первом этапе поддерживать x64 и AArch64, а поддержку других наборов инструкций добавлять по мере того, как этот режим в статусе Experimental будет становиться готовым к использованию по умолчанию.

Что не является целью

  • Заменить Shenandoah без поколений не является целью: он останется режимом работы по умолчанию, без регрессий в производительности или функциональности.

  • Улучшить производительность для любой мыслимой нагрузки не является целью. Система с поколениями будет при необходимости динамически подстраиваться, приближаясь к системе без поколений, но для некоторых нагрузок начать с одного поколения и остаться с ним всё равно может оказаться лучшим вариантом. Тем не менее мы ожидаем, что большинство сценариев использования выиграет от сборки мусора с поколениями.

  • Снизить потребление процессорного времени и энергии по сравнению с традиционными сборщиками мусора с остановкой мира (stop-the-world) не является целью. Если допустимы более длинные паузы, другие сборщики, например G1, по-прежнему могут работать энергоэффективнее. Generational Shenandoah может лишь приблизиться к эффективности приёмов сборщиков с остановкой мира, но никогда не сравняется с ними, поскольку обязан держать время пауз намного ниже и полностью избегать уплотнения с остановкой мира. Однако в этом отношении Generational Shenandoah будет значительно ближе к нынешним сборщикам с поколениями и остановкой мира, чем Shenandoah без поколений.

  • Максимизировать пропускную способность мутатора не является целью. Если допустимы более длинные паузы, другие сборщики, например Parallel, на некоторых платформах по-прежнему могут обеспечивать более высокую пропускную способность.

  • В первом выпуске эргономические эвристики могут работать не оптимально на всех нагрузках.

Критерии успеха

  • Generational Shenandoah сравнивается с Shenandoah без поколений на бенчмарках SPECjbb2015, HyperAlloc, Extremem и Dacapo.

  • Рабочие диапазоны (то есть сочетания скорости выделения памяти, заполненности кучи и целевого времени паузы) для HyperAlloc, Extremem и подобных нагрузок сравниваются с Shenandoah без поколений. Успешные запуски сокращают или полностью устраняют задержки выделения памяти (allocation stalls) и необходимость в полных или вырожденных сборках.

Мотивация

Сборщики мусора с конкурентным уплотнением способны полностью вписать паузы GC в диапазон единиц миллисекунд, характерный для других обычных пауз JVM, и при этом почти не замедлять выполнение мутатора. Shenandoah без поколений уже обеспечивает такое идеальное поведение GC для Java-приложений, чувствительных к задержке. Однако достичь этого он может только в ограниченных рабочих диапазонах (то есть при определённых сочетаниях заполненности кучи и скорости выделения памяти).

Классический способ минимизировать средние затраты на GC — принять гипотезу о поколениях, согласно которой большинство объектов умирает молодыми, и сосредоточить циклы сборки на молодых, а значит, в основном мёртвых объектах. По сравнению со сборщиками с поколениями G1, CMS и Parallel, Shenandoah без поколений обычно требует большего запаса кучи и тратит больше усилий на освобождение места, занятого недостижимыми объектами.

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

Конкурентный сборщик, который к тому же использует поколения и может динамически менять размер молодого поколения и связанные рабочие параметры, способен одновременно обеспечить короткие паузы и оставаться конкурентоспособным по другим показателям производительности.

Описание

Это улучшение сборщика мусора Shenandoah разделяет кучу Java на два поколения. Как и в других сборщиках с поколениями, усилия GC сосредоточены на молодом поколении, то есть на том, в котором мутатор выделяет память и где короткоживущие объекты можно освободить с меньшими затратами. Для первоначальной реализации мы предлагаем следующий подход.

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

Каждое поколение образовано подмножеством регионов кучи Shenandoah. В любой момент регион считается либо свободным, либо отданным молодому или старому поколению. Размер каждого поколения определяется занятыми им регионами плюс квотой свободных регионов. Заход в свободную квоту другого поколения допускается, но он ускоряет запуск сборки и может привести к вырожденным и полным сборкам. Мы активно дорабатываем алгоритмы, управляющие планированием фаз сборки, размером молодого поколения, возрастом перевода в старое поколение (tenuring age) и другими механизмами автонастройки.

У Shenandoah есть уникальный барьер Load Reference Barrier (LRB), который поддерживает 32-битные сборки и сжатые 32-битные указатели на объекты («compressed oops») в 64-битных сборках. Чтобы ограничить влияние на мутатор, мы используем этот же LRB для обоих поколений без каких-либо изменений и единый механизм эвакуации для сборки и старого, и молодого поколения. Типичные фазы эвакуации собирают мусор либо только из молодых регионов, либо из сочетания молодых и старых регионов. Такое поведение повторяет молодые и смешанные сборки G1. Главное улучшение по сравнению с G1 в том, что молодые и смешанные сборки Generational Shenandoah выполняются конкурентно с мутатором.

Фазы разметки для разных поколений в значительной мере независимы друг от друга. Конкурентная разметка старого поколения идёт в фоне, пока несколько раз выполняются разметка и эвакуация молодого поколения. Разметку старого поколения можно прервать, чтобы выполнить более приоритетные сборки молодого поколения. После завершения разметки старого поколения последующие эвакуации и обновления ссылок включают регионы старого поколения, пока не будет обработан весь набор сборки (collection set) старого поколения.

Для реализации remembered set мы используем существующий код card marking, заимствованный из реализаций Parallel и CMS GC, и дополняем его новым кодом, который позволяет сканировать remembered set конкурентно с выполнением мутатора.

Существующие SATB-барьеры Shenandoah без поколений обобщены так, чтобы обслуживать общие потребности конкурентной разметки молодого и старого поколений. При последующей обработке SATB-буферов ссылки на память старого поколения обрабатываются иначе, чем ссылки на память молодого поколения, но быстрый путь через эти барьеры остаётся неизменным.

Использование

Новая возможность работы с поколениями входит в кодовую базу Shenandoah, но никак не влияет на выполнение программы, пока не включена параметрами командной строки JVM

-XX:+UnlockExperimentalVMOptions -XX:ShenandoahGCMode=generational

в этом случае Shenandoah будет использовать Generational Mode.

В вики проекта будут приведены подробности о том, как настроить JVM для эффективной работы в Generational Mode приложений, использующих Shenandoah GC.

Альтернативы

Сборщик C4 компании Azul Systems уже использует поколения, но недоступен в открытом исходном коде. Generational ZGC (ZGC с поколениями) был реализован в JDK 21. Ни один из этих вариантов не поддерживает сжатые указатели на объекты. Однако подавляющее большинство куч Java, которые мы видим (например, в облачных сервисах), значительно меньше 32 ГБ и потому может пользоваться этой возможностью, которая экономит память и повышает производительность.

Тестирование

Большинство существующих функциональных и стресс-тестов не зависят от сборщика и могут использоваться повторно без изменений. Мы добавим дополнительные конфигурации запуска тестов для нового Generational Mode, а также новые функциональные тесты, тесты производительности и стресс-тесты для этого режима.

Сейчас оптимизация производительности сосредоточена на x86 и AArch64 под Linux. SAP портировала Generational Mode на PowerPC и протестировала его на этой платформе. CI-тесты запускаются на Linux, macOS и Windows. Поддержку Generational Mode для других платформ можно реализовать и оптимизировать позже.

Риски и допущения

  • Операции с remembered set, в частности сканирование, могут увеличить время пауз.

  • Барьеры, связанные с remembered set, могут увеличить накладные расходы мутатора.

  • Эвристики для автоматической настройки размеров поколений, политики продвижения объектов, а также времени запуска и распределения усилий между сборками молодого и старого поколений всё ещё разрабатываются и проверяются на реальных нагрузках. Пока для оптимальной производительности может потребоваться ручная настройка.