PunchArmy recovers X account after after hack attemptThe Jerusalem PostAn impossible equation: Eisenkot won the mandate, but can he secure a coalition? - analysisInquirerIlocos Norte, Ilocos Sur among areas that have recovered from ASF한겨레‘복수혈전’ 완성한 사자 군단…삼성, 12년 만에 KS 우승ZDF heuteEntdecken Sie das ZDF-NachrichtenstudioThe South African‘RACIAL HATRED’ | Roland Schoeman blasts Julius MalemaIl Fatto QuotidianoUna pista da sci di plastica alle porte di Palermo: “Portiamo gli sport di montagna in Sicilia”. Dubbi su impatto ambientale e paesaggisticoRTL BoulevardMessi eerste speler die voor tweede jaar op rij topscorer MLS isFotogramasQuentin Tarantino, sobre su décima y última película: "No pretendo ser provocador, pero tengo que decir que he escrito un guion que podría llevar a cabo y que me gusta muchísimo"SRF NewsPolen – Tödliche Schüsse in Krakau – das ist bekanntThe Sydney Morning HeraldChapman stripped of ball, Fiji scoresYonhap SportsLions defeat Wiz for 1st Korean Series title in 12 yrs
The Daily Newsstand · Free, Always
Sunday, November 8, 2026

[Перевод] Улучшения производительности в JDK 27

Translate

В JDK 27 продолжается планомерная работа над производительностью всех компонентов JDK. После выхода JDK 26 в проект OpenJDK внесли более 2300 коммитов, включая изменения в библиотеках, сборщиках мусора, JIT-компиляторе и среде исполнения.

Одно из изменений затронет особенно широкий круг приложений: компактные заголовки объектов (Compact Object Headers) теперь включены по умолчанию. Среди других изменений — использование G1 в качестве сборщика мусора по умолчанию во всех окружениях.

Если не указано иное, приведённые ниже числа взяты из JMH-бенчмарков или измерений на рабочих нагрузках, опубликованных в соответствующих пул-реквестах. Они показывают локальный эффект конкретного изменения, а не его влияние на приложение в целом. На результаты влияют оборудование, характеристики входных данных, размер кучи, сборщик мусора, прогрев и состояние компиляции кода.

Улучшения библиотек JDK

Ленивые константы: третья предварительная версия

JEP 531 представляет третью предварительную версию (preview) ленивых констант — Lazy Constants. Объект LazyConstant<T> вычисляет значение при первом обращении и инициализируется только один раз. После инициализации он возвращает то же значение — для объекта это означает ту же ссылку, хотя состояние самого объекта может меняться. JVM может использовать это свойство примерно так же, как в случае поля final. Это позволяет применять оптимизации, например свёртку констант, без немедленной инициализации или ручной реализации блокировки с двойной проверкой (double-checked locking).

В JDK 27 удалены низкоуровневые методы isInitialized() и orElse(): их использование могло нарушать задуманную модель инициализации. Кроме того, к существующим фабричным методам создания ленивых списков и отображений добавлен метод Set.ofLazy(...). Поскольку Lazy Constants остаются preview API, для их использования по-прежнему нужен флаг --enable-preview.

Ускорение массового копирования в HashMap

Раньше HashMap.putAll() и конструктор HashMap(Map) копировали записи через интерфейс Map. При таком подходе создаётся итератор и вызываются методы next(), getKey(), getValue() и hashCode(). Если через одну точку вызова проходят несколько реализаций Map, вызовы могут стать мегаморфными, что затрудняет их оптимизацию JIT-компилятором. Неизменяемая обёртка добавляет ещё один уровень виртуальных вызовов.

JDK-8371656 (PR #28243) добавляет специализированные пути выполнения для случаев, когда источником служит именно HashMapлибо немодифицируемая обёртка над ним. Реализация напрямую обходит внутреннюю таблицу источника и повторно использует хеш, сохранённый в каждом узле. Это позволяет обойтись без итератора и повторного вычисления хеш-кодов.

В измерениях на AWS Graviton через точки вызова намеренно пропускали пять типов Map. Время выполнения операции сократилось на 61% для HashMap из пяти записей и на 86% для неизменяемой обёртки над HashMap из 150 записей. Во втором случае результат бенчмарка снизился примерно с 10 593 до 1533 нс/операцию. Для остальных типов исходных отображений по-прежнему используется общий путь выполнения.

Ускорение обработки текста с атрибутами

Приложения, которые выполняют компоновку текста, например графические интерфейсы на Swing, могут неожиданно много времени проводить в AttributedString. Атрибуты участков текста хранились в двух массивах объектов Vector, поэтому поиск атрибута для каждого символа мог требовать линейного поиска через Vector.indexOf().

JDK-8380794 (PR #30402) заменяет эту структуру одним массивом отображений. В представленном бенчмарке обход текста с одним или несколькими атрибутами занимал на 35–40% меньше времени. При создании строки с одним атрибутом также выделялось примерно на 20% меньше памяти. С четырьмя атрибутами разница составляла около 1%, а без атрибутов была пренебрежимо мала. Изменение может ускорить код, использующий AttributedString, в том числе API для измерения текста и разбиения его на строки.

Более эффективное преобразование URL в строку

URL.toString() делегирует работу методу URLStreamHandler.toExternalForm(), который также встречается в профилях запуска приложения и загрузки классов. Старая реализация создавала промежуточные строки, объединяя отдельные компоненты, и не имела специализированного пути для распространённого случая, когда необязательные компоненты отсутствуют.

JDK-8379557 (PR #30151) объединяет конкатенацию в одну операцию и добавляет быстрые пути для распространённых вариантов URL. В представленных бенчмарках время выполнения сократилось как минимум на 60% — в зависимости от того, какие компоненты authority, строки запроса и фрагмента присутствовали в URL. Ускорение сохранилось и в тестах с выполнением только в интерпретаторе. Это показывает, что эффект не зависит исключительно от исполнения скомпилированного кода.

Устранён лишний системный вызов при открытии файлов

В Unix классы FileOutputStream и RandomAccessFile после открытия файла для записи вызывали fstat, чтобы исключить запись в каталог. Однако соответствующая операция open и так завершается ошибкой EISDIR в этом случае, поэтому дополнительный системный вызов был избыточным.

JDK-8373647 (PR #28823) удаляет этот вызов. В стресс-бенчмарке время выполнения снизилось примерно с 3722 до 3192 нс/операцию, то есть на 14%. Выигрыш на уровне приложения зависит от того, насколько часто оно открывает файлы вместо повторного использования уже открытых. При этом изменение убирает один переход в режим ядра из распространённого пути выполнения API.

Ускорение криптографических операций

В JDK 27 появилось несколько улучшений криптографического кода: как алгоритмических, так и специфичных для отдельных архитектур.

  • JDK-8376164 (PR #29385) позволяет интринсику AES/ECB для x86 обрабатывать сообщение целиком и параллельно выполнять побитовое XOR с раундовыми ключами вместо вызова нативной подпрограммы для каждого 16-байтового блока. На Intel Core i9-14900HX время выполнения в представленном бенчмарке сократилось примерно с 11,52 до 8,38 мкс/операцию. Это соответствует росту пропускной способности примерно на 37%.

  • JDK-8384353 (PR #31125) добавляет интринсик SHA-3 с использованием AVX2 и повышает параллелизм в реализации для AVX-512. Представленные бенчмарки показали улучшение производительности вычисления хеша SHA-3 на 16–39% с AVX2 и на 24–33% с AVX-512. Ускорились и операции ML-DSA и ML-KEM, использующие SHA-3. Последующее изменение JDK-8386911 (PR #31648) удалило лишние вызовы Keccak, не выполнявшие полезной работы, и устранило регрессии в конфигурациях без нового интринсика.

  • JDK-8383635 (PR #30991) улучшает умножение с использованием теоретико-числового преобразования, применяемое в ML-KEM. Представленные результаты показывают улучшение производительности инкапсуляции и декапсуляции на 9–17% для всех трёх размеров ключей.

  • В порт для RISC-V добавлены интринсики AES/CTR (JDK-8365732, PR #25243), AES/CBC (JDK-8371968, PR #28320) и GHASH (JDK-8373069, PR #28548). На AArch64 изменение JDK-8371459 (PR #28515) выбирает интринсик SHA-3, использующий регистры общего назначения, на тех процессорах, где он работает быстрее.

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

Другие улучшения библиотек

  • JDK-8378698 (PR #29920) позволяет Base64.Encoder.encodeToString() использовать внутренний доверенный конструктор String, избегая копирования массива.

  • Два изменения значительно сокращают разрыв в производительности между CharsetEncoder.canEncode(CharSequence) и canEncode(char) для распространённых встроенных кодировок: JDK-8376226 (PR #29391) и JDK-8381015 (PR #30456).

  • JDK-8375580 (PR #29288) и JDK-8376403 (PR #29430) устраняют использование ArrayDeque на ранних этапах работы URLClassPath и ZipFile соответственно.

  • JDK-8376477 (PR #29442) позволяет не загружать пустые вспомогательные классы блокировок, используемые в Shutdown и ReferenceQueue.

  • JDK-8381710 (PR #31142) устраняет использование лямбд в ModuleBootstrap во время обучающего запуска для AOT, сокращая сопутствующую загрузку классов.

Перечисленные улучшения библиотек не гарантируют повышения итоговых показателей. В наших тестах минимальные приложения на JDK 27 запускаются примерно за то же время, что и на JDK 26, или немного медленнее, при этом используя до 1 МБ меньше памяти.

Сборка мусора

G1 теперь используется по умолчанию во всех окружениях

Уже много релизов HotSpot выбирает G1 по умолчанию на машинах серверного класса. Однако в небольших окружениях или при ограниченных ресурсах он всё ещё мог выбирать Serial GC. JEP 523 делает G1 сборщиком по умолчанию во всех окружениях в JDK 27.

Это прежде всего изменение ради единообразия, а не утверждение, что G1 подходит для любой нагрузки. Оно особенно важно для небольших контейнеров, окружений с одним процессором и машин разработчиков, которые раньше не соответствовали эвристике определения серверного класса. Даже без изменений в приложении в таких окружениях могут измениться длительность пауз, потребление CPU и дополнительные расходы памяти. Serial GC по-прежнему доступен через -XX:+UseSerialGC.

Сопутствующее изменение JDK-8367993 (PR #28723) откладывает большую часть инициализации механизма конкурентной маркировки G1 до момента, когда он понадобится. Теперь буферы статистики и потоки конкурентной маркировки не нужно создавать на самом раннем этапе запуска виртуальной машины. Представленные результаты показывают небольшое сокращение времени создания VM. Теперь это улучшение затрагивает больше окружений, поскольку G1 стал универсальным вариантом по умолчанию.

Предотвращение патологического поведения GC

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

  • JDK-8358342 (PR #31610) устраняет плохую масштабируемость наборов корней из скомпилированного кода (code-root sets), которые G1 поддерживает для каждого региона. Пользователи сообщали о паузах длительностью более 90 секунд, когда сканирование и расширение этой структуры становились слишком затратными.

  • JDK-8377561 (PR #29673) исправляет циклическую ситуацию в Parallel GC: выделение больших объёмов памяти могло вызывать повторяющиеся полные сборки мусора (Full GC), при этом куча не расширялась настолько, чтобы приложение могло продолжить работу.

  • JDK-8379846 (PR #31189) снижает чувствительность адаптивного расчёта порога заполнения кучи для запуска маркировки в G1 к единичному выбросу скорости выделения памяти. Раньше такой выброс мог приводить к бесконечным конкурентным циклам. JDK-8381006 (PR #30625) исправляет тот же расчёт при включённых периодических сборках мусора.

Shenandoah с поддержкой поколений также продолжает развиваться. В JDK 27 улучшена эвакуация объектов старого поколения (JDK-8376839, PR #29511); из буферов переноса объектов в старшее поколение убраны лишняя регистрация каждого объекта и операции выравнивания по границам карточек (JDK-8385606, PR #31316); а во время выделения памяти под очень большие массивы теперь можно приостановить поток для перехода в безопасную точку (safepoint) (JDK-8379531, PR #30567).

Улучшения компилятора

Оптимизация большего числа вариантов векторного кода

Vector API остаётся в статусе incubator уже двенадцатый релиз. При этом улучшения компилятора C2 ускоряют как код, явно использующий Vector API, так и обычные циклы.

  • JDK-8342095 (PR #23413) добавляет в автоматический векторизатор SuperWord поддержку преобразований типов для значений разрядностью меньше машинного слова. Теперь циклы, которые сужают или расширяют значения byte и short, в большем числе случаев могут сохранять векторную форму вместо перехода к скалярному выполнению.

  • JDK-8370863 (PR #28313) удаляет избыточные цепочки преобразований векторных масок и распознаёт последовательности «загрузка — преобразование типа — сохранение», в которых значение не меняется. В специализированных JMH-тестах на системе NVIDIA Grace со 128-битным SVE2 пропускная способность выросла примерно в 2,3–3,8 раза в зависимости от типа элементов и структуры операций.

Другие изменения добавляют эффективные операции редукции для поиска минимума и максимума среди беззнаковых значений на x86 (JDK-8346256, PR #29751) и AArch64 (JDK-8372980, PR #28693). JDK-8358521 (PR #25617) меняет группировку операций со значениями, размноженными по всем элементам вектора (broadcast). Некоторые векторные операции заменяются более дешёвым скалярным вычислением с последующим однократным размножением результата. Вместе эти изменения позволяют JIT-компилятору преобразовывать больше разновидностей циклов и операций Vector API в эффективные SIMD-инструкции.

Улучшение упрощений скалярных выражений и устранение регрессий

Анализ KnownBits в C2 теперь применяется к большему числу целочисленных выражений. JDK-8367341 (PR #27618) использует известные значения битов и границы беззнаковых значений для упрощения узлов And и Or, а JDK-8278857 (PR #29546) распознаёт больше комбинаций сдвига и побитового И. Улучшения обработки тождеств для операций минимума и максимума (JDK-8373396, PR #28770; JDK-8374896, PR #29517) также помогают выявлять константы и избыточные вычисления, которые можно устранить.

Часть работы восстанавливает оптимизации, утраченные из-за предыдущих изменений компилятора. Например, JDK-8378713 (PR #30090) восстанавливает свёртку констант в тех случаях использования Math.pow(), где возникла регрессия. Такие исправления редко попадают в число главных новостей релиза, но они не дают обновлению незаметно замедлить код, который уже был хорошо оптимизирован.

Улучшения среды исполнения

Компактные заголовки объектов включены по умолчанию

В JDK 24 компактные заголовки объектов были экспериментальной возможностью, а в JDK 25 стали полноценной функцией. JEP 534 завершает её внедрение: в JDK 27 компактные заголовки включены по умолчанию.

В типичной 64-битной конфигурации HotSpot заголовок каждого объекта уменьшается с 12 до 8 байт. Относительная экономия наиболее заметна в кучах с большим количеством небольших объектов. Более плотное размещение данных в кеше и сокращение объёма данных, которые сборщику мусора нужно сканировать и копировать, могут дать выигрыш не только в потреблении памяти.

В JEP 519, который сделал эту возможность готовой к использованию в продакшене, приводились следующие измерения: снижение использования кучи на 22% и потребления CPU на 8% в одной из конфигураций SPECjbb2015; сокращение числа сборок мусора на 15% как с G1, так и с Parallel GC в другой конфигурации; уменьшение времени выполнения примерно на 10% в бенчмарке JSON-парсера с высокой степенью параллелизма. Результаты сильно зависят от распределения размеров объектов и их выравнивания. Приложение, в котором преобладают большие массивы примитивов, получит меньший выигрыш, чем приложение с преимущественно небольшими объектами.

Компактные заголовки можно отключить с помощью -XX:-UseCompactObjectHeaders. Это полезно для A/B-сравнения при обновлении, а также как временный обходной путь, если низкоуровневый инструмент или агент рассчитывает на прежнюю структуру объектов в памяти.

Таблица мониторов объектов включена по умолчанию

JDK-8379782 (PR #30331) включает таблицу мониторов объектов (Object Monitor Table) по умолчанию. Таблица хранит соответствие между Java-объектами и мониторами, развёрнутыми в полноценную структуру (inflated monitors), за пределами заголовка объекта. Отключение таблицы с помощью -XX:-UseObjectMonitorTable возвращает прежнее представление, при котором указатели на мониторы размещаются в заголовках объектов.

Новый штатный флаг -XX:-UseObjectMonitorTable был объявлен устаревшим сразу при добавлении, поскольку он предназначен для переходного периода. В одном из будущих релизов планируется удалить и флаг, и прежнюю реализацию. Включение таблицы по умолчанию выявило регрессию производительности на 30% в бенчмарке с интенсивной синхронизацией. JDK-8382311 (PR #30988) устранило её, сократив число попыток активного ожидания в механизме быстрых блокировок перед развёртыванием монитора при конкуренции потоков.

Улучшения JFR

  • JDK-8373490 (PR #28781) устраняет патологическое поведение профилировщика утечек, которое могло вызывать длительные задержки при обработке больших массивов объектов.

  • JDK-8382740 (PR #30880) отключает событие jdk.OldObjectSample при использовании ZGC с поддержкой поколений. Образцы объектов, на которые профилировщик хранит слабые ссылки, могли сохраняться до сборки старого поколения и вызывать длительные остановки при выделении памяти. Это временный отказ от одной из возможностей профилирования ради более предсказуемого поведения приложения.

Дополнительные материалы

  • Предыдущие статьи этой серии посвящены JDK 24, JDK 25 и JDK 26.

  • Статья Sven Woltmann Compact Object Headers in Java содержит иллюстрированное объяснение структуры заголовков и их влияния на потребление памяти.

  • В материале Billy Korando JDK 27 Runtime Updates Release Notes представлен короткий видеоролик — 2 минуты — об основных возможностях JDK 27.

  • Статья Mohamed Aboullaite Java 27 features даёт более широкий обзор релиза. Страница релиза OpenJDK JDK 27, а также приведённые выше ссылки на JEP, JBS и пул-реквесты остаются первичными источниками точной информации о статусе изменений и деталях реализации.

Заключение

Обычные рекомендации по обновлению остаются в силе, но теперь к ним добавляется ещё одна: проверяйте новые настройки по умолчанию по отдельности. Сначала измерьте работу приложения на JDK 27 с обычными настройками, а затем меняйте по одному параметру, например, используйте -XX:-UseCompactObjectHeaders или выберите другой сборщик мусора, чтобы объяснить существенные различия. Измеряйте не только пиковую пропускную способность, но и время запуска, скорость выделения памяти, объём живых объектов, задержки в хвосте распределения и потребление CPU.

В JDK 28 уже включён или находится в разработке целый ряд улучшений производительности. Мы планируем рассказать о них весной 2027 года.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.