JDK 26 выходит уже совсем скоро. Тем временем в GC закрыли около 380 задач (почти в 2 раза больше, чем в прошлом релизе), но в этот раз акцент сместился с больших фич в пользу практичных доработок.
Главное для всех сборщиков: нормальный учет CPU GC. Теперь считают не только stop-the-world паузы, но и конкурентную работу и дедупликацию строк. Можно посмотреть через лог cpu=info при завершении VM, обновили Hsperf-счетчики, есть доступ из кода. Плюс новый JFR-ивент с деталями по string dedup.
JEP 516: Aot Cache стал независим от выбранного GC и опций VM. Включение через опцию -XX:+AOTStreamableObjects.
G1 получил самые заметные улучшения: JEP 522 уменьшает синхронизацию между GC и приложением (цель - увеличить throughput). Еще: целевое использование CPU G1 по умолчанию снижено с 8% до 4%, добавили важнейший флаг UseGCOverheadLimit.
Комментарий от Михаила Поливаха
Важно и то, что HotSpot GC команда в Oracle смотрит в сторону Automatic Heap Sizing. На эту темы было много шума на Redit и не зря. Поговорим об этом на подкасте.
Проект OpenJDK собирается выпустить JDK 26 уже через несколько дней. Это хороший повод добавить информацию о заметных изменениях в stop-the-world-сборщиках в HotSpot VM.
Полный список исправленных/закрытых задач по подкомпоненту GC для JDK 26 находится здесь; всего закрыто или разрешено около 380 изменений.
В JDK 26 закрыто почти вдвое больше задач, чем в прошлом релизе. Краткий анализ показывает, что состав изменений иной: вместо нескольких крупных — много небольших, как заметных конечным пользователям, так и относящихся лишь к качеству кода. Большинство крупных изменений интегрировали рано, поэтому основное время разработки пришлось на более ранние периоды релизного цикла. Кроме того, несколько новых участников, работавших над компонентом GC, внесли ощутимый вклад.
Начнём с разбивки, на мой взгляд, изменений, о которых стоит упомянуть, сгруппированных по stop-the-world-сборщикам мусора для JDK 26.
Все сборщики
В этом релизе появилось значительное число изменений, влияющих на все сборщики мусора (включая не stop-the-world):
Заметно улучшен учёт использования CPU сборщиком мусора. Теперь VM отслеживает потребление CPU GC более полно, включая как время внутри GC-пауз, так и процессорное время, потраченное на конкурентную работу (например, конкурентную разметку), а также на смежные задачи вроде дедупликации строк.
В прежних релизах информация была фрагментирована или требовала ручной агрегации, и пользователям было сложно либо вовсе невозможно надёжно её получить.
Теперь есть несколько способов получить эти данные — один из них: включить логирование cpu=info, которое печатает примерно следующее при завершении VM:
[info ][cpu] === CPU time Statistics ========================================= [info ][cpu] CPUs [info ][cpu] s % utilized [info ][cpu] Process [info ][cpu] Total 443.4045 100.00 9.0 [info ][cpu] Garbage Collection 365.2198 82.37 7.4 [info ][cpu] GC Threads 365.1982 82.36 7.4 [info ][cpu] VM Thread 0.0216 0.00 0.0 [info ][cpu] =================================================================
Вывод показывает использование CPU процессом в целом и сборкой мусора — в абсолютных и относительных величинах (JDK-8359110). И ещё - счётчики Hsperf были немного обновлены; точные имена новых счётчиков см. в JDK-8315149. Есть и программный способ получать эту информацию прямо из вашего приложения.
Комментарий от Михаила Поливаха
Счетчики Hsperf (или же HotSpot Performance Counters) это просто счетчики, которые поддерживает HotSpot рантайм, там хранится самая разная телеметрия.
Хотя вы напрямую с этими счетчиками редко взаимодейтсвуете (но и это можно через условный jstat), добрая часть знакомых Вам profiler-ов (VisualVM, JMC и т.д.) их читает и на основании их отображает информацию по потокам, по загруженным классам и т.д.
В этой статье одного из моих коллег об анализе взаимосвязи CPU и памяти в сборке мусора показано и использовано это новое измерение.
JEP 516: Ahead-of-Time Object Caching with Any GC вводит новый механизм загрузки заранее (AOT) связанных и загруженных объектов с явной целью хорошо работать со всеми сборщиками мусора HotSpot. Причина — независимая от алгоритма сборки мусора поддержка Project Leyden.
Предыдущий формат AOT-кэша зависел от алгоритма GC и настроек VM, что усложняло расширение, особенно в сторону ZGC. Новый формат независим и от GC, и от опций VM.
Недостаток в том, что новый алгоритм требует больше работы на старте. Реализация JEP 516 снижает влияние на старт, наполняя объекты конкурентно с запуском приложения во время старта.
Чтобы включить новый формат, используйте новую опцию командной строки -XX:+AOTStreamableObjects.
Повышена надёжность процесса завершения работы. До JDK 26 агент JVMTI мог выделять память уже после того, как подсистема сборки мусора была остановлена, что приводило к неожиданному поведению — зависаниям, ошибкам нехватки памяти или падениям.
К релевантным задачам относятся JDK-8367902 и JDK-8366865.
Для заметного числа опций командной строки изменились значения по умолчанию либо они были помечены как устаревшие с последующим удалением.
-XX:InitialRAMPercentage, определяющая объём Java-кучи, который коммитится при старте как процент от установленной памяти, теперь по умолчанию равна 0. То есть по-умолчанию теперь общий объём установленной памяти больше не влияет на начальный размер Java-кучи. (JDK-8371987).
На практике это означает, что объём памяти, коммитящийся VM при запуске (и влияющий на время старта), теперь определяется только минимальным размером кучи, выведенным другими механизмами (
-XX:MinHeapSize/-Xms/-XX:InitialHeapSizeи др.), вместо произвольной инициализации процента от установленной RAM под Java-кучу на старте.CSR для этого изменения подробнее объясняет мотивацию.
-XX:AggressiveHeap, включающая набор оптимизаций главным образом для «долго работающих, интенсивно использующих память бенчмарков», также помечена как устаревающая. Сразу несколько причин — включая слишком общее имя, ориентацию на SPECjbb и сложность подбора сопоставимого набора настроек для нынешнего крайне разнообразного ландшафта таких приложений — привели к планируемому удалению. В CSR перечислены опции, которые она включает, если кто-то захочет вручную воспроизвести её эффект.
Набор малоизвестных опций, обычно применяемых только для внутреннего тестирования, которые помечены как устаревающие и будут удалены в следующих релизах:
-XX:MaxRAM(JDK-8369346) и-XX:AlwaysActAsServerClassMachine(JDK-8370844). Их эффект аналогично можно заменить другими опциями.Наконец, появился новый JFR-ивент, который детализирует результаты дедупликации строк (JDK-8360540), уменьшая необходимость полагаться на интерпретацию логов.
Parallel GC
Parallel GC в JDK 26 в основном получил ряд «чистящих» изменений. Самое важное для конечных пользователей, вероятно, — депрекация и перевод в устаревшие нескольких опций VM.
Депрекация опции -XX:ParallelRefProcEnabled (JDK-8359924) — то, что с наибольшей вероятностью затронет пользователей. Она управляет распараллеливанием фазы обработки java.lang.ref.Reference. Эта опция давно включена по умолчанию и не вызывала проблем, поэтому причин сохранять её не было. В частности, если когда-либо нужно уменьшить параллелизм этой фазы, можно использовать -XX:ReferencesPerThread, меняя объём работы на поток и добиваясь того же эффекта.
Сборщик G1 тоже использовал эту опцию, поэтому на него это изменение влияет так же.
Кроме того, несколько малоизвестных опций, специфичных для Parallel GC, были помечены как устаревающие, переведены в obsolete или удалены: -XX:PSChunkLargeArrays (JDK-8360628), -XX:HeapMaximumCompactionInterval (JDK-8366882) и доступная только в debug-сборках GCExpandToAllocateDelayMillis (JDK-8363229).
Serial GC
Хотя были некоторые значимые внутренние изменения (например, пространства Eden и Survivor поменяли местами в адресном пространстве (JDK-8368740)) и рефакторинг, крупных пользовательских заметных изменений в этом релизе не было.
G1 GC
G1 получил наибольшее число изменений среди stop-the-world-сборщиков, помимо общих.
Самое крупное и, вероятно, самое заметное единичное изменение для G1 за долгое время — интеграция JEP 522: G1: Improve Throughput By Improving Synchronization.
В более раннем посте изменение описано подробно: по сути, объём синхронизации памяти между сборщиком и приложением в G1 был снижен почти до уровня Serial и Parallel GC (хотя здесь есть потенциал для дальнейшего улучшения). Это повышает пропускную способность и делает G1 более конкурентоспособным по отношению к Parallel GC, с минимальными компромиссами по другим атрибутам алгоритма, которые делают G1 удачным выбором по умолчанию: низкие, предсказуемые паузы, масштабируемость и разумное потребление нативной памяти. Теперь G1 должен обеспечивать почти «сырую» пропускную способность Parallel GC без долгих полных сборок по всей куче.
Это важная веха на пути к тому, чтобы сделать G1 единственным настоящим сборщиком по умолчанию, хотя требуется ещё работа.
На протяжении этого релизного цикла основное внимание было сосредоточено на работе по автоматическому определению размера кучи (Automatic Heap Sizing) для G1. Цель — чтобы G1 подбирал размер Java-кучи с учётом условий окружения: если свободной памяти много — использовать больше ресурсов, если это помогает производительности; но снижать суммарное потребление памяти VM по мере того, как соседние процессы или другие условия системы уменьшают доступную память. Один из результатов — в будущем во многих деплойментах должно быть меньше необходимости вручную настраивать максимальный размер кучи (-Xmx/-XX:MaxHeapSize).
Шаги в этом направлении включают более частую переоценку использования памяти Java-кучей (JDK-8238687) на основе использования CPU. Это связано с более корректным мониторингом использования CPU сборщиком (JDK-8359348) — теперь и конкурентная работа также учитывается при расчётах CPU-потребления GC.
Это согласуется с давно назревшим снижением целевого уровня использования CPU G1 по умолчанию (JDK-8247843). Значение по умолчанию уменьшается с 8% до 4%. Изначальное значение относится к самым первым релизам G1, но с годами G1 стал намного быстрее и гораздо более стабильно ведёт себя на широком спектре нагрузок, часто сохраняя ту же производительность при более «тесной» куче и меньшем потреблении CPU сборщиком, чем раньше.
Одна из наиболее часто запрашиваемых функций из мира Parallel collector — поддержка -
XX:UseGCOverheadLimit(JDK-8212084). Когда она активна (а по умолчанию это так), VM выбрасывает Out-Of-Memory исключение, если использование CPU сборкой мусора остаётся выше порога (-XX:GCTimeLimit, по умолчанию 98%), а свободное место в Java-куче ниже другого порога (-XX:GCHeapFreeLimit, по умолчанию 2%) длительное время. Замысел — избежать ситуации, когда сильно неверно настроенная VM тратит почти всё время на GC, но приложение при этом не может продвигаться.
Комментарий от Михаила Поливаха
Вот действительно это очень важная доработка.
Обратите внимание, данная опция как раз решает ту проблему, которую в свое время пытался решить Netflix и о которой мы писали когда-то</a>. Речь про ситуацию, когда GC занимает 90%+ пропускной способности приложения, при этом не допуская OOM, но и не давая приложению делать прогресс. В итоге сервис как бы работает, но условно говоря запросы к нему улетают по timeout-у.
Во-вторых, G1 везде GC по-умолчанию. Многие команды не тюнят GC вообще. Поэтому, для многих эта опция актуальна.
Сообщения логов на уровне gc=info и ниже дают дополнительный контекст о том, как именно были достигнуты условия срабатывания.
Как обычно, были и общие улучшения производительности: некоторое распараллеливание (например, JDK-8363932) и улучшение механизма раннего освобождения (eager reclaim): поведение, описанное здесь, теперь применяется ко всем типам огромных (humongous) объектов (JDK-8048180). CR содержит график производительности, показывающий, как это может уменьшить неожиданно частые или длительные сборки в сценариях с ограниченным запасом по памяти.
Наконец, улучшения старта (например, JDK-8371019) помогают Project Leyden.
Что дальше
Наш фокус остаётся на Automatic Heap Sizing и улучшении управления различными аспектами управления памятью, чтобы G1 — и в целом все stop-the-world-сборщики — могли разумнее реагировать на внешние условия и намерения пользователя. Параллельно продолжается работа по JEP 401 в области сборки мусора.

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.
