Daily MaverickSPRAY TELL: Bread & Wine: The chemical trail through wheat and grapes (Part Five)InquirerPhilippines has enough natural fibers for textile growth—DARTP Desporto18h30 Portugal aflito no voleibol e com F1 de regressoThe Jerusalem PostA recipe for disaster: Edward Miliband, Britain, and relations with Israel - opinionESPNPackers left without answers after blowing another lead lateESPN Deportes¿LeBron James ganará un título con su cuarto equipo en la NBA?וואלהדיווח איראני: איש דת סוני נורה למוות ע"י חמושים לא מזוהים בדרום מזרח המדינהCapital FMRuto: Govt to Support Raila Family in Anniversary PreparationsCBS NewsEPA to eliminate limits on climate pollution from power plantsTechCrunchClickFix attacks are tricking Mac and Windows users into hacking themselvesConsequenceLin-Manuel Miranda Announces Definitive Hamilton Box Set for 10th AnniversaryDeadline‘Yaga’ Sets U.S. Release Date On AMC+; Watch Sneak Peek Ahead Of TIFF World Premiere
The Daily Newsstand · Free, Always
Monday, September 14, 2026

Выходит Java 27

Translate

15 сентября 2026 года вышла Java 27. Это не LTS-релиз, и новых возможностей языка, ради которых команды бросятся переписывать код, здесь немного. Зато поведение самой JVM меняется достаточно заметно.

Компактные заголовки объектов включаются по умолчанию, G1 становится стандартным сборщиком мусора даже в окружениях с небольшим количеством ресурсов, а Java Flight Recorder начинает автоматически скрывать пароли, токены и другие чувствительные значения.

Для Spring Boot-приложения это может означать меньше занятый heap без изменений в коде. А может означать неожиданно другой профиль потребления CPU в маленьком контейнере или неработающий стартовый скрипт из-за удалённого JVM-флага.

Разберёмся, что именно меняется и что стоит проверить до обновления.

Коротко

В Java 27 есть три важных изменения runtime:

  1. Compact Object Headers включены по умолчанию. Во многих приложениях это способно сократить занятый heap на 10–20%, но результат зависит от структуры объектов.

  2. G1 теперь выбирается по умолчанию во всех окружениях. Маленький контейнер, который раньше запускался с Serial GC, после обновления может получить другой профиль памяти, CPU и пауз.

  3. JFR автоматически редактирует чувствительные данные в аргументах JVM, переменных окружения и системных свойствах.

Кроме того, удалены некоторые устаревшие JVM-флаги, изменился JSON-формат дампов потоков и появились новые диагностические возможности jcmd.

Откуда в Java-объекте лишние байты

Объект в heap состоит не только из объявленных нами полей. JVM хранит рядом служебную информацию:

  • состояние блокировки;

  • данные, связанные с identityHashCode;

  • возраст объекта для сборщика мусора;

  • ссылку на описание класса объекта.

В типичной 64-битной JVM с включёнными compressed class pointers заголовок объекта занимал 12 байт:

  • 8 байт для mark word;

  • 4 байта для ссылки на класс.

Из-за выравнивания объект без полей обычно занимал не 12, а 16 байт. Получалась любопытная ситуация: экземпляр пустого класса потреблял больше памяти на устройство JVM, чем на собственные данные.

final class Marker {
}

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

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

Что делают Compact Object Headers

Компактные заголовки объединяют compressed class pointer и mark word. В результате минимальный заголовок на 64-битной JVM уменьшается с 96–128 до 64 бит.

Фича прошла несколько стадий:

  • в JDK 24 она была экспериментальной;

  • в JDK 25 стала финальной, но требовала -XX:+UseCompactObjectHeaders;

  • в JDK 27 включается по умолчанию.

То есть после обновления больше не нужно добавлять специальный флаг. Отключить механизм для диагностики всё ещё можно:

java -XX:-UseCompactObjectHeaders -jar application.jar

Проверить текущее значение параметра можно так:

java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders

Действительно ли heap уменьшится на 20%

В материалах OpenJDK и Inside Java встречается оценка экономии heap в диапазоне 10–20%. Но её нельзя читать как обещание уменьшить потребление любого приложения ровно на 20%.

Результат зависит от объектного графа.

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

Если же основную часть heap занимают большие массивы byte[], кэши с крупными значениями или несколько тяжёлых структур, относительная экономия окажется скромнее.

Есть и важное различие между размером live set и лимитом heap. Если контейнер запускается с -Xmx2g, Java 27 не превратит этот параметр в -Xmx1600m. Уменьшиться может объём памяти, который занимают живые объекты. Это потенциально даёт:

  • больше свободного пространства внутри прежнего heap;

  • более редкие циклы сборки мусора;

  • меньше перемещаемых данных;

  • лучшую локальность данных в процессорном кэше;

  • возможность позднее уменьшить -Xmx, если это подтвердят измерения на вашем ворклоаде.

Сначала нужно сравнить метрики, и только затем менять лимиты контейнера.

Как проверить эффект на Spring Boot-приложении

Лучше всего сравнивать две конфигурации одной и той же Java 27:

java -XX:+UseCompactObjectHeaders -jar application.jar

и:

java -XX:-UseCompactObjectHeaders -jar application.jar

Так мы изолируем влияние компактных заголовков от остальных изменений между версиями JDK.

Во время теста стоит смотреть не только на максимальный RSS процесса, но и на:

  • размер live set после полной сборки;

  • частоту и длительность GC;

  • allocation rate;

  • количество старых объектов;

  • время прогрева;

  • p95 и p99 latency;

  • CPU на одинаковом профиле нагрузки.

Для анализа структуры отдельных классов можно использовать Java Object Layout:

System.out.println(
    org.openjdk.jol.info.ClassLayout
        .parseClass(OrderDto.class)
        .toPrintable()
);

Но JOL показывает layout конкретного объекта, а не экономику приложения целиком. Для итогового вывода всё равно понадобится профиль под реалистичной нагрузкой.

G1 теперь используется даже на маленьких машинах

G1 считается сборщиком мусора по умолчанию с JDK 9, но существовало исключение. Если JVM определяла окружение как машину с ограниченными ресурсами, она могла выбрать Serial GC.

В Java 27 это исключение убрали. Теперь G1 используется по умолчанию во всех окружениях, если сборщик не указан явно.

Узнать выбранный GC можно командой (или из GarbageCollectorMXBean):

java -Xlog:gc -version

В Java 27 без дополнительных параметров в журнале должна появиться строка о G1.

Для обычного серверного приложения это выглядит логично. G1 умеет параллельно обрабатывать части heap и стремится удерживать паузы в заданных пределах. Но более современный GC не означает «лучший GC для любого процесса».

Serial GC прост, использует меньше служебных структур и может быть вполне разумным выбором для маленьких утилит, однопроцессорных окружений и сервисов с очень небольшим heap.

Почему это важно для Kubernetes

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

Представим Spring Boot-сервис с такими ресурсами:

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "384Mi"

На предыдущей версии JDK он мог автоматически получить Serial GC. После перехода на Java 27 тот же сервис без изменения параметров запуска получит G1.

Это способно изменить:

  • количество GC-потоков;

  • потребление CPU во время сборки;

  • служебные расходы GC;

  • длительность пауз;

  • поведение приложения при приближении к memory limit.

Если Serial GC был осознанным выбором, его лучше зафиксировать явно:

java -XX:+UseSerialGC -jar application.jar

Если сборщик никогда не указывался, перед обновлением нужно снять фактическую конфигурацию текущей JVM и сравнить её с Java 27. Иначе изменение будет незаметно в коде и манифестах, но проявится в продакшн-метриках.

JFR больше не должен записывать токены открытым текстом

Java Flight Recorder собирает подробную диагностическую информацию с небольшими накладными расходами. Это делает JFR удобным для использования не только локально, но и в продакшене.

Проблема в том, что вместе с полезными данными в запись могли попасть:

  • аргументы запуска JVM;

  • переменные окружения;

  • системные свойства.

Именно через эти механизмы приложения часто получают секреты:

export PAYMENT_API_TOKEN=super-secret-value

java \

  -Dspring.datasource.password=another-secret \

  -XX:StartFlightRecording=filename=application.jfr,duration=60s \

  -jar application.jar

Если такой файл попадал в тикет, общий каталог или систему хранения диагностики, секрет мог утечь вместе с ним.

В Java 27 JFR по умолчанию скрывает значения, названия которых похожи на:

  • password;

  • passwd;

  • token;

  • secret;

  • credential;

  • api-key;

  • private-key;

  • client-secret.

Сопоставление выполняется без учёта регистра и поддерживает glob-шаблоны.

Можно добавить собственное имя:

java \

  -XX:FlightRecorderOptions:redact-key=+dburl \
  -jar application.jar

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

Для аргументов JVM используется отдельный параметр:

java \
  -XX:FlightRecorderOptions:redact-arguments=@redact-patterns.txt \
  -jar application.jar

Посмотреть доступные настройки можно через встроенную справку -XX:FlightRecorderOptions.

Почему одной редактуры JFR недостаточно

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

Во-первых, фильтрация основана на именах и шаблонах. Переменная PAYMENT_API_TOKEN будет распознана, а условная переменная ACCESS может не попасть под стандартное правило.

Во-вторых, приложение способно самостоятельно записать чувствительное значение в пользовательское JFR-событие, лог или exception message.

В-третьих, JFR-файл всё равно содержит подробности о работе приложения и должен считаться диагностическим артефактом с ограниченным доступом.

Получается, что автоматическая редактура является дополнительным защитным слоем, а не заменой Vault, Kubernetes Secrets, IAM и нормальной политики доступа к диагностике.

Какие старые JVM-флаги перестанут работать

В Java 27 окончательно удалены:

-noclassgc
-noverify
-verifyremote
-Xverify:none

Для -noclassgc существует замена:

-Xnoclassgc

Для -verifyremote:

-Xverify:remote

У -noverify и -Xverify:none прямой замены нет.

Последние два флага нередко оставались в старых шаблонах запуска, Dockerfile и конфигурациях IDE как попытка ускорить старт. После перехода на Java 27 JVM завершит запуск с ошибкой, поэтому искать их нужно заранее:

grep -R --line-number \
  -e=-noverify \
  -e=-Xverify:none \
  -e=-noclassgc \
  -e=-verifyremote \
  .

Также параметр:

-XX:InitiatingHeapOccupancyPercent

переименован в:

-XX:G1IHOP

Старое имя пока работает, но уже помечено устаревшим.

Опция -XX:[+|-]UseCompressedClassPointers стала obsolete. JVM всегда использует compressed class pointers, а передача старого параметра приведёт к предупреждению.

Ещё несколько изменений для эксплуатации

В jcmd появилась команда, которая показывает активные security properties работающей JVM:

jcmd <pid> VM.security_properties

Это полезно при разборе проблем с TLS, криптографическими провайдерами и алгоритмами, когда итоговая конфигурация отличается от ожидаемой.

VM.info и аварийные файлы hs_err_pid теперь содержат текущее количество открытых файловых дескрипторов. При расследовании падения можно быстрее заметить, что процесс подошёл к системному лимиту.

Изменился и JSON-формат thread dump. Идентификаторы потоков, их количество и PID теперь записываются числами, а не строками:

{
  "tid": 42
}

В корневом объекте появился formatVersion со значением 2.

Если внутренние инструменты разбирают JSON-дампы в строго типизированную модель и ожидают "tid": "42", после обновления они могут перестать работать. Это тот редкий breaking change, который затрагивает не приложение, а окружающую его диагностическую инфраструктуру.

Наконец, из JDK удалён экспериментальный JVM Compiler Interface, включая jdk.internal.vm.ci, встроенные компоненты Graal compiler и флаг -XX:+UseGraalJIT. Обычные Spring Boot-приложения этого не заметят, но нестандартные сборки и инструменты, которые полагались на JVMCI внутри OpenJDK, нужно проверить отдельно.

Стоит ли переходить

Java 27 не является LTS-релизом. Для большинства компаний основной production-версией останется Java 25, а следующей LTS станет Java 29.

Но пропускать Java 27 при тестировании не стоит.

Во-первых, Compact Object Headers уже стали финальной возможностью в Java 25. Java 27 показывает, каким будет стандартное поведение будущих версий.

Во-вторых, смена GC в ограниченных окружениях затрагивает именно ту область, где автоматические решения JVM часто остаются незаметными до появления странных графиков в продакшене.

В-третьих, автоматическая редактура данных JFR делает диагностику безопаснее и, вероятно, позволит большему числу команд использовать Flight Recorder постоянно.

Java 27 интересна не новым синтаксисом. Она меняет стоимость существующих объектов, правила выбора GC и безопасность эксплуатационных данных. Код может остаться прежним, но профиль приложения уже будет другим.

Именно поэтому этот релиз стоит проверять не глазами компилятора, а нагрузочными тестами, JFR и метриками контейнера.

Источники

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

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.