Привет! Меня зовут Антон Богомазов, я backend-разработчик в продуктовой команде Домклик. Наш проект представляет собой более десяти Kotlin/Spring-микросервисов, развернутых в Kubernetes, и постоянно растет, поэтому мы неизбежно сталкиваемся с растущим потреблением ресурсов кластера. Это обстоятельство и подтолкнуло меня к поиску технологий, позволяющих оптимизировать расходы на содержание наших сервисов. В этой статье я хочу исследовать возможности технологии Java Native Image, поделиться опытом взаимодействия с ней и со средствами Spring для генерации нативных образов. Native image — технология, позволяющая скомпилировать Java-код в исполняемый файл. Для поддержки этой функциональности существует Spring Native, использующий GraalVM для генерации образов. Главное преимущество такого подхода в том, что можно мгновенно запустить приложение без старта JVM, тратить меньше памяти и иметь меньший размер файла. Еще одним плюсом является отсутствие прогрева приложения, так как компиляция выполнялась до запуска. Но есть и недостатки: создание native image требует значительно больше времени, чем сборка Java-приложения; отсутствие runtime-оптимизаций снижает пиковую производительность. Также не всякое приложение может быть представлено в виде native image, а использование некоторых фич потребует дополнительной конфигурации. С полным списком ограничений можно ознакомиться здесь. Чтобы опробовать возможности технологии в деле, создадим простое приложение: контроллер с методом, возвращающим случайное число в ответ на GET-запрос. Прежде всего установим GraalVM, который требуется Spring Native; нас интересуют его возможности AOT-компиляции. Скачайте по ссылке https://github.com/graalvm/graalvm-ce-builds/releases, а содержимое положите в JavaVirtualMachines. Теперь можно приступить к созданию приложения; я воспользуюсь Spring Initializr. Добавим зависимости Spring Native [Experimental] и Spring Reactive Web. Последняя не является обязательной, но сделает код проще за счет Reactive Kotlin DSL: Создадим класс контроллера с одним методом: Приложение готово! Подготовительный этап закончен и можно приступить к сравнению. С помощью nativeCompile в Gradle я создал native image и JAR одного и того же приложения и сравнил различные их показатели: Разница во времени создания колоссальная, компиляция нативного образа требует большого количества системных ресурсов, что отражается на длительности создания. У меня ушло около 4 минут даже на такое простое приложение. С native image потребление RAM удалось сократить на 7 %. Сравнить размеры файлов оказалось довольно сложно. JAR для своего запуска требует наличие JRE в окружении, а native image уже содержит все необходимые компоненты, поэтому я прибавил к размеру JAR 46 мб — размер среднего JRE. Поэтому размер образа оказался также на 7 % меньше. Длительность запуска составила 5,84 и 0,72 секунды для JAR и native image соответственно, обещания мгновенного старта оказались не пустыми словами. Так как измерения во многом приблизительные, вместо цифр я приведу диаграмму, которая, тем не менее, красноречиво описывает свойства каждого из подходов: Попробуем ответить на главный вопрос: где это можно применить? Я вижу несколько сфер: Прежде всего, веб-приложения и FaaS. Очень быстрый старт позволяет поднимать и гасить реплики значительно быстрее, чем если бы это были обычные Java-приложения. С другой стороны, это сработает не так хорошо с приложениями, зависимыми от инфраструктуры: подключение к БД и брокерам сообщений отнимает ценное время. Десктопные приложения. Компиляция исполняемого файла позволяет запускать на машине без JVM, что снижает требования к среде выполнения и расширяет области применения Java. Итоги Уменьшенный размер образа и, как следствие, пода позволит оптимизировать ресурсы K8s-кластера, потенциально позволяет держать большую нагрузку за счет большего количества реплик. Для еще более радикального уменьшения размера можно сочетать native image с distroless. Но чаще дефицитным ресурсом является RAM. Остается без ответа вопрос, будет ли полученная оптимизация масштабирована соответственно размеру приложения, и можно ли её увеличить тонкими настройками при создании образа? Это требует отдельного исследования. Кроме того, функциональность Spring Native является экспериментальной, что может стать аргументом против её использования в вашем проекте. Для себя я решил, что проект недостаточно зрелый, но, безусловно, интересный, буду наблюдать за его развитием.