Перейти к основному содержимому

Ускорить время запуска Spring Boot

· 7 мин. чтения

1. Введение

В этом руководстве мы рассмотрим различные конфигурации и настройки, которые могут помочь сократить время запуска Spring Boot. Во-первых, мы рассмотрим конкретные конфигурации Spring. Во-вторых, мы рассмотрим параметры виртуальной машины Java. Наконец, мы расскажем, как мы можем использовать GraalVM и компиляцию собственного образа для дальнейшего сокращения времени запуска.

2. Весенние настройки

Прежде чем мы начнем, давайте настроим тестовое приложение. Мы будем использовать Spring Boot версии 2.5.4 с Spring Web, Spring Actuator и Spring Security в качестве зависимостей. В pom.xml мы добавим spring-boot-maven-plugin с конфигурацией для упаковки нашего приложения в jar-файл:

<plugin> 
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<finalName>springStartupApp</finalName>
<mainClass>com.foreach.springStart.SpringStartApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>

Запускаем наш jar-файл стандартной командой java -jar и следим за временем запуска нашего приложения:

c.b.springStart.SpringStartApplication   : Started SpringStartApplication in 3.403 seconds (JVM running for 3.961)

Как мы видим, наше приложение запускается примерно через 3,4 секунды. Мы будем использовать это время в качестве ориентира для будущих настроек.

2.1. Ленивая инициализация

Spring Framework поддерживает ленивую инициализацию. Ленивая инициализация означает, что Spring не будет создавать все bean-компоненты при запуске. Кроме того, Spring не будет вводить зависимости до тех пор, пока этот компонент не понадобится. Начиная с Spring Boot версии 2.2. можно включить ленивую инициализацию с помощью application.properties : ``

spring.main.lazy-initialization=true

После создания нового jar-файла и его запуска, как в предыдущем примере, новое время запуска немного лучше:

c.b.springStart.SpringStartApplication   : Started SpringStartApplication in 2.95 seconds (JVM running for 3.497)

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

Кроме того, отложенная инициализация имеет преимущества во время разработки при использовании функции горячего перезапуска DevTools. Увеличение количества перезапусков с отложенной инициализацией позволит JVM лучше оптимизировать код.

Однако ленивая инициализация имеет несколько недостатков. Самый существенный минус в том, что приложение будет медленнее обслуживать первый запрос. Поскольку Spring требуется время для инициализации необходимых bean-компонентов, другим недостатком является то, что мы можем пропустить некоторые ошибки при запуске. Это может привести к ClassNotFoundException во время выполнения.

2.2. Исключение ненужной автоконфигурации

Spring Boot всегда отдавал предпочтение соглашению, а не конфигурации. Spring может инициализировать bean-компоненты, которые не требуются нашему приложению. Мы можем проверить все автоматически сконфигурированные bean-компоненты, используя журналы запуска. Установка уровня ведения журнала DEBUG на org.springframework.boot.autoconfigure в application.properties :

logging.level.org.springframework.boot.autoconfigure=DEBUG

В логах мы увидим новые строки, посвященные автонастройке, начиная с:

============================
CONDITIONS EVALUATION REPORT
============================

С помощью этого отчета мы можем исключить части конфигурации приложения. Чтобы исключить часть конфигурации, используем аннотацию @EnableAutoConfiguration :

@EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class, JvmMetricsAutoConfiguration.class, 
LogbackMetricsAutoConfiguration.class, MetricsAutoConfiguration.class})

Если бы мы исключили библиотеку Jackson JSON и некоторые настройки метрик, которые мы не используем, мы могли бы сэкономить время при запуске:

c.b.springStart.SpringStartApplication   : Started SpringStartApplication in 3.183 seconds (JVM running for 3.732)

2.3. Другие мелкие доработки

Spring Boot поставляется со встроенным контейнером сервлетов. По умолчанию мы получаем Tomcat. Хотя Tomcat достаточно хорош в большинстве случаев, другие контейнеры сервлетов могут быть более производительными . В тестах Undertow от JBoss показывает себя лучше, чем Tomcat или Jetty. Он требует меньше памяти и имеет лучшее среднее время отклика. Чтобы переключиться на Undertow, нам нужно изменить pom.xml :

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Следующее небольшое улучшение может быть в сканировании classpath. Сканирование путей к классам Spring — это быстрое действие. Мы можем улучшить время запуска, создав статический индекс, когда у нас большая кодовая база. Нам нужно добавить зависимость к spring-context-indexer для создания индекса. Spring не требует дополнительной настройки. Во время компиляции Spring создаст дополнительный файл в META-INF\spring.components . Spring будет использовать его автоматически при запуске:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>${spring.version}</version>
<optional>true</optional>
</dependency>

Поскольку у нас есть только один компонент Spring, эта настройка не дала значительных результатов в наших тестах.

Далее есть несколько допустимых мест для файлов application.properties (или .yml) . Чаще всего они находятся в корне пути к классам или в той же папке, что и файл jar. Мы можем избежать поиска в нескольких местах, установив явный путь с параметром spring.config.location и сэкономив пару миллисекунд при поиске:

java -jar .\target\springStartupApp.jar --spring.config.location=classpath:/application.properties

Наконец, Spring Boot предлагает несколько компонентов MBean для мониторинга нашего приложения с помощью JMX. Полностью отключите JMX и избегайте затрат на создание этих bean-компонентов:

spring.jmx.enabled=false

3. Настройки JVM

3.1. Подтвердить флаг

Этот флаг устанавливает режим проверки байт-кода. Проверка байт-кода показывает, правильно ли отформатированы классы и соответствуют ли они ограничениям спецификации JVM. Мы устанавливаем этот флаг на JVM во время запуска.

Есть несколько вариантов этого флага:

  • -Xverify — это значение по умолчанию, которое включает проверку для всех классов, не являющихся загрузчиками.
  • -Xverify:all включает проверку всех классов. Эта настройка окажет значительное негативное влияние на производительность стартапов.
  • -Xverify:нет (или -Xnoverify ). Эта опция полностью отключает верификатор и значительно сокращает время запуска.

Мы можем передать этот флаг при запуске: **

**

java -jar -noverify .\target\springStartupApp.jar

Мы получим предупреждение от JVM о том, что этот параметр устарел. Также уменьшится время запуска:

c.b.springStart.SpringStartApplication   : Started SpringStartApplication in 3.193 seconds (JVM running for 3.686)

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

3.2. Флаг многоуровневой компиляции

В Java 7 появилась многоуровневая компиляция . Компилятор HotSpot будет использовать разные уровни компиляции кода.

Как мы знаем, код Java сначала интерпретируется в байт-код. Затем байт-код компилируется в машинный код. Этот перевод происходит на уровне метода. Компилятор C1 компилирует метод после определенного количества вызовов. После еще большего количества запусков компилятор C2 компилирует его, еще больше увеличивая производительность.

Используя флаг -XX:-TieredCompilation , мы можем отключить промежуточные уровни компиляции. Это означает, что наши методы будут интерпретированы или скомпилированы компилятором C2 для максимальной оптимизации. Это не приведет к снижению скорости запуска. Что нам нужно, так это отключить компиляцию C2. Мы можем сделать это с опцией -XX:TieredStopAtLevel=1 . В сочетании с флагом -noverify это может сократить время запуска. К сожалению, это замедлит работу JIT-компилятора на более поздних этапах.

Один только флаг TieredCompilation приносит существенное улучшение:

c.b.springStart.SpringStartApplication   : Started SpringStartApplication in 2.754 seconds (JVM running for 3.172)

Для дополнительного удовольствия, совместное использование обоих флагов из этого раздела еще больше сокращает время запуска:

java -jar -XX:TieredStopAtLevel=1 -noverify .\target\springStartupApp.jar
c.b.springStart.SpringStartApplication : Started SpringStartApplication in 2.537 seconds (JVM running for 2.912)

4. Родная весна

Нативный образ — это код Java, скомпилированный с помощью опережающего компилятора и упакованный в исполняемый файл. Для запуска не требуется Java. Полученная программа работает быстрее и меньше зависит от памяти, так как нет накладных расходов JVM. Проект GraalVM представил собственные образы и необходимые инструменты сборки.

Spring Native — это экспериментальный модуль, поддерживающий собственную компиляцию приложений Spring с использованием компилятора собственного образа GraalVM. Упреждающий компилятор выполняет несколько задач во время сборки, что сокращает время запуска (статический анализ, удаление неиспользуемого кода, создание фиксированного пути к классам и т. д.). Есть еще некоторые ограничения для нативных изображений:

  • Он не поддерживает все функции Java
  • Отражение требует специальной настройки
  • Ленивая загрузка классов недоступна
  • Совместимость с Windows является проблемой.

Чтобы скомпилировать приложение в собственный образ, нам нужно добавить зависимость spring-aot и spring-aot-maven-plugin в pom.xml. Maven создаст собственный образ в команде package в целевой папке.

5. Вывод

В этой статье мы рассмотрели различные способы улучшения времени запуска приложений Spring Boot. Во-первых, мы рассмотрели различные функции, связанные со Spring, которые могут помочь сократить время запуска. Далее мы показали параметры, специфичные для JVM. Наконец, мы представили Spring Native и собственное создание образов. Как всегда, код, использованный в этой статье, можно найти на GitHub .