В OpenJDK предложили JEP 544, ускорение старта Java на 65, 80% для микросервисов
JEP 544, предложение в рамках проекта OpenJDK Leyden, посвящённого ускорению старта и прогрева Java-приложений. Оно расширяет уже существующий AOT-кэш (AOT, Ahead-of-Time, «компиляция заранее», в противовес JIT, Just-in-Time, «компиляции по требованию») виртуальной машины HotSpot, эталонной JVM для Java. Раньше в кэш попадали только загруженные и связанные классы (JEP 483, доставлен в JDK 24) и профили выполнения методов (JEP 515, доставлен в JDK 25); JEP 544 добавляет туда же готовый оптимизированный машинный код, скомпилированный заранее, на тренировочном запуске приложения.
Проблема, которую это решает: при обычном запуске HotSpot проходит через старт (интерпретирует байт-код и грузит классы), прогрев (собирает профиль выполнения и параллельно компилирует «горячие» методы простым компилятором C1) и только затем выходит на пик, когда самые нагруженные методы перекомпилированы продвинутым компилятором C2 в полностью оптимизированный код. Весь этот путь повторяется заново при каждом запуске JVM, и именно он делает Java медленным стартёром по сравнению со статической компиляцией, что особенно заметно на короткоживущих процессах вроде микросервисов, где приложение может завершиться раньше, чем дойдёт до пиковой производительности.
Технически ничего не меняется в интерфейсе: тренировочный запуск создаёт кэш командой java -XX:AOTCacheOutput=app.aot ..., рабочие запуски подключают его через -XX:AOTCache=app.aot ..., тот же процесс, что и раньше, просто кэш теперь ещё и хранит машинный код. Ни код приложения, ни библиотеки, ни фреймворки менять не нужно; отдельно включать генерацию или использование AOT-кода тоже не нужно, HotSpot делает это по умолчанию, а если готового кода нет, он несовместим или устарел (нагрузка приложения изменилась), виртуальная машина как обычно откатывается к интерпретатору и JIT-компиляции и переоптимизирует «горячие» методы заново. Авторы отдельно оговаривают, что это не режим «только AOT»: JIT-компиляция, профилирование и деоптимизация остаются работать параллельно. Поддержаны сборщики мусора Serial, Parallel, G1 и ZGC и только две архитектуры процессора, AArch64 и x64; кросс-компиляция не предусмотрена, тренировочный и рабочий запуски должны выполняться на одной архитектуре с одинаковым набором процессорных возможностей.
По замерам самих авторов, на пяти тестовых Java-приложениях (конкретные фреймворки не названы) на двухъядерной машине Linux/x64, конфигурация выбрана специально, чтобы JIT-компилятору пришлось конкурировать с самим приложением за процессорное время, как в реальном микросервисе, AOT-кэш без машинного кода сокращает время старта на 50, 70%, а с добавленным AOT-кодом (то есть после JEP 544), на 65, 80%. В отдельном тесте на прогрев, javac, повторно компилирующий одни и те же 50 файлов 20 раз, уже первая итерация выигрывает около 75% времени старта (30% даёт сам кэш без кода, ещё 45% добавляет AOT-код), а кривая производительности с AOT-кодом выходит на плато («устоявшееся» состояние полностью прогретой JVM) уже к четвёртой итерации, заметно раньше, чем без кэша.
У самого JEP 544 статус Candidate, а целевую версию JDK текст не называет, в отличие от предшественников JEP 483 и JEP 515, для которых версии названы прямо (JDK 24 и JDK 25 соответственно). Авторы предложения при этом названы в шапке страницы (Owner, John Rose, Reviewed by, Alex Buckley, Dan Heidinga, Mark Reinhold, Vladimir Kozlov, Endorsed by, Vladimir Kozlov); не названы конкретные фреймворки и приложения из тестов, а точных цифр по цене вопроса, сколько дополнительного места на диске и времени тренировочного запуска стоит хранение машинного кода в кэше, текст не приводит.
Ключевые факты
- JEP 544 (проект OpenJDK Leyden) расширяет AOT-кэш JVM HotSpot: помимо классов (JEP 483, JDK 24) и профилей выполнения (JEP 515, JDK 25) он теперь хранит готовый оптимизированный машинный код, скомпилированный на тренировочном запуске.
- На пяти тестовых Java-приложениях на двухъядерной машине Linux/x64 (условие выбрано, чтобы эмулировать конкуренцию за процессор, как в микросервисе) AOT-кэш без машинного кода ускоряет старт на 50, 70%, а с добавленным AOT-кодом, на 65, 80%.
- В тесте на прогрев (javac, 50 файлов, 20 повторных компиляций) первая итерация выигрывает около 75% времени старта, 30% даёт сам кэш и ещё 45% добавляет AOT-код, а кривая с AOT-кодом выходит на плато уже к четвёртой итерации.
- Схема использования не меняется: тренировочный запуск с флагом -XX:AOTCacheOutput, рабочие запуски, с -XX:AOTCache; изменений в коде приложений, библиотек или фреймворков не требуется.
- Ограничения: поддержаны только архитектуры AArch64 и x64, тренировочный и рабочий запуск должны совпадать по процессору (кросс-компиляция не предусмотрена), статус самого предложения, Candidate, а целевая версия JDK в тексте не указана.
Почему это важно
Старт и прогрев, историческая слабость Java рядом с языками со статической компиляцией: пока HotSpot интерпретирует байт-код и постепенно докомпилирует «горячие» методы простым компилятором C1, а затем продвинутым C2, статически скомпилированное приложение выходит на пик почти мгновенно. У статической компиляции есть своя цена, она не умеет подстраиваться под смену нагрузки в приложении, плохо переносится между разным железом, ОС и версиями JDK и не дружит с динамическими механизмами Java (динамическая загрузка классов, рефлексия). JEP 544 не выбирает одну из сторон, а переносит часть работы компиляции на более раннее время: тренировочный запуск один раз компилирует «горячие» методы, а каждый рабочий запуск просто подгружает готовый код из кэша, сохраняя деоптимизацию и повторную оптимизацию как страховку на случай изменения нагрузки. Это завершает работу, начатую двумя предыдущими шагами того же проекта Leyden, кэшированием классов (JEP 483) и профилей (JEP 515), и именно этот, самый дорогой элемент даёт наибольший измеренный прирост: 65, 80% против 50, 70% у кэша без машинного кода.
Кому это важно
Прежде всего, командам, которые держат много короткоживущих процессов JVM: автоматически масштабируемые микросервисы, бессерверные функции на Java и любые пайплайны, где JVM запускается и завершается часто (не случайно тестом на прогрев выбран javac, сам компилятор Java, который в CI гоняют десятками раз за одну сборку). Для таких сценариев холодный старт, не разовая цена, а постоянный налог на каждый новый инстанс. Авторы прямо оговаривают цель, не требовать изменений в коде приложений, библиотек или фреймворков и не менять конфигурацию HotSpot сверх согласия на использование AOT-кэша: значит, выигрыш достаётся без переписывания сервисов, при условии перехода на версию JDK, где JEP 544 станет доступен (когда это произойдёт, текст не говорит).
Как это применить
Схема не меняется по сравнению с уже существующим AOT-кэшем. Тренировочный запуск, отражающий типичную нагрузку, выполняется с флагом -XX:AOTCacheOutput=app.aot, в файл app.aot попадают классы, профили выполнения и, начиная с JEP 544, оптимизированный машинный код горячих методов. Рабочие запуски подключают тот же файл через -XX:AOTCache=app.aot; отдельно включать генерацию или использование AOT-кода не нужно, HotSpot делает это по умолчанию. Важное практическое условие: тренировочный и рабочий запуски должны выполняться на одной архитектуре процессора с одинаковым набором его возможностей, кросс-компиляция не поддерживается, а из архитектур пока заявлены только AArch64 и x64. Работает с основными сборщиками мусора HotSpot, Serial, Parallel, G1 и ZGC, так что выбор сборщика мусора сам по себе не мешает подключить AOT-код.
Можно ли доверять
Первоисточник, страница самого предложения на openjdk.org, то есть первичный документ проекта OpenJDK, а не пересказ третьих лиц; для описания механизма это самый надёжный уровень источника. У цифр производительности другой статус: замеры проводили сами авторы предложения (в шапке страницы, Owner John Rose, Reviewed by Alex Buckley, Dan Heidinga, Mark Reinhold, Vladimir Kozlov, Endorsed by Vladimir Kozlov), в тексте они пишут об этом от первого лица множественного числа («мы прогнали пять тестовых приложений»), а сами приложения и оборудование не названы конкретно, кроме «двухъядерная машина Linux/x64», то есть это самозаявленные результаты разработчиков, а не независимое воспроизведение сторонней командой. Статус предложения указан в той же шапке, кандидат (Candidate), то есть ещё не назначено к выпуску и не интегрировано; целевую версию JDK текст при этом не называет, в отличие от JEP 483 и JEP 515, где версии (JDK 24 и JDK 25) названы прямо. До перехода предложения в статус Targeted или Integrated разумно читать 65, 80% как заявленный разработчиками потенциал, а не как гарантированный результат в конкретном релизе.
Риски и подводные камни
Главное ограничение встроено в сам механизм: кэш, собранный на тренировочном запуске, привязан к архитектуре процессора и набору его возможностей, переезд на другое железо, другую ОС или другую версию JDK требует новой тренировки, а без неё AOT-код просто не подойдёт (JVM тогда откатится к интерпретатору и JIT-компиляции, то есть корректность не пострадает, но и обещанного ускорения не будет). Список поддержанных архитектур пока короткий, только AArch64 и x64. Это не режим «только AOT»: если нагрузка в проде изменится настолько, что закэшированный код перестанет подходить, HotSpot всё равно деоптимизирует и переоптимизирует методы заново, то есть от нетипичной нагрузки кэш не страхует, он лишь снижает частоту такого прогрева. Наконец, в тексте нет цифр по цене решения, сколько дополнительно весит кэш на диске и сколько времени добавляет тренировочный запуск, хотя текст отдельно предупреждает, что кэшированный код способен заметно увеличить AOT-кэш, и даёт диагностический флаг AOTCodeCaching для оценки этого роста: решение о переходе на AOT-код приходится принимать, зная о росте расходов лишь качественно, без точных цифр.