Почему Spring Boot стартует 8 секунд и как это лечить
«Spring Boot тяжёлый и долго стартует» — общее место. Но за фразой стоит конкретный набор причин, и каждая лечится по-своему. Разберём, на что реально уходят секунды старта, и три уровня оптимизации — от бесплатной до радикальной.
На что тратится время при старте
Холодный старт типичного Spring Boot-приложения (Spring Boot 3.x, Java 21, средний CRUD-сервис) обычно занимает от 3 до 10 секунд на современном железе (точные цифры сильно зависят от размера classpath и числа бинов; см. замеры Spring Boot Startup Report и блог SpringIO). Эти секунды раскладываются на несколько слоёв:
1. Classpath-сканирование. Spring ищет классы с @Component, @Service, @Configuration, @Controller по всему classpath. Большое приложение = тысячи файлов, каждый открывается и парсится.
2. Автоконфигурация. Spring Boot стартует десятки @AutoConfiguration-классов, каждый обёрнут @ConditionalOnClass / @ConditionalOnMissingBean. Большинство условий проверяется через reflection — медленно.
3. Hibernate и метамодель. Если есть JPA-сущности, Hibernate при старте строит метамодель, валидирует схему, генерирует SQL. Для десятка сущностей это сотни миллисекунд, для сотен — секунды.
4. Прокси и AOP. Каждый @Transactional, @Async, @Cacheable метод заставляет Spring генерировать CGLIB-прокси. Это reflection и генерация байткода в рантайме.
5. Разогрев JIT. HotSpot начинает с интерпретации, JIT подключается постепенно. Первые запросы медленные, пока горячие методы не откомпилируются.
Это не баг, а следствие того, что Spring — runtime-DI-фреймворк: большую часть работы он делает при запуске, а не при компиляции.
Уровень 1: ленивая инициализация (бесплатно)
Самый дешёвый способ отнять секунды — не стартовать то, что не нужно прямо сейчас. В application.properties:
spring.main.lazy-initialization=true
Бины создаются не при старте, а при первом обращении. Старт ускоряется, потому что Hibernate не строит метамодель для всех сущностей сразу, прокси для всех бинов не создаются и т.д.
Цена: первый запрос будет медленным. Если лень загружает тяжёлый сервис по первому HTTP-запросу пользователя — он увидит задержку. Поэтому ленивая инициализация подходит для development-окружения и для serverless-функций, где это всё равно произойдёт, но не рекомендуется для long-running production без понимания, какие бины трогать.
Точечный вариант — пометить @Lazy отдельные тяжёлые бины, а не всё приложение.
Уровень 2: CDS и AOT-обработка (Spring 6)
Spring 6 и Spring Boot 3 завезли processing-time (AOT) — фреймворк анализирует конфигурацию на этапе сборки и генерирует готовые описания бинов. Это убирает часть reflection в рантайме.
CDS (Class Data Sharing) — механизм JVM, который сохраняет обработанные метаданные классов в архив и переиспользует их при следующих запусках, минуя повторную загрузку и верификацию. Spring Boot умеет создавать CDS-архив:
# Создать CDS-архив при первом прогоне
java -XX:ArchiveClassesAtExit=app.jsa -jar app.jar
# Дальше запускать с готовым архивом
java -XX:SharedArchiveFile=app.jsa -jar app.jar
По замерам Spring-команды (вебинары Spring Academy), CDS сокращает старт типичного приложения на 15–30% без изменения кода. Это середина между «бесплатно» и «радикально» — нужно настроить CI, чтобы архив собирался.
Уровень 3: GraalVM native-image (радикально)
Если старт действительно критичен — serverless-функции, scale-to-zero в Kubernetes, CLI-утилиты — есть GraalVM. native-image превращает Java-приложение в standalone-исполняемый файл без JVM: старта за десятки миллисекунд, минимальное потребление памяти.
# В Spring Boot 3 — встроенная поддержка
./gradlew nativeCompile # или mvn -Pnative package
На выходе — бинарник, который стартует за 30–80 мс вместо 3–10 секунд. Цифры из презентаций Spring и GraalVM (но проверяйте на своём workload).
Компромиссы, без которых native не понять:
- Build-time analysis. GraalVM на этапе сборки определяет, какие классы и методы реально нужны, и выбрасывает остальное. Всё, что не видно статически, исчезает.
- Reflection нужно регистрировать. Если код динамически грузит класс по имени (
Class.forName), Native Image об этом не знает. Spring делает большую работу за вас через@RegisterReflectionи AOT-метаданные, но сторонние библиотеки могут ломаться. - Build time растёт. Сборка native-образа занимает минуты, не секунды.
- Peak throughput ниже. Native-образ не имеет разогретого JIT — он использует Graal's ahead-of-time компилятор. Для long-running сервиса под постоянной нагрузкой обычная JVM с разогретым JIT быстрее в steady state. Native выигрывает именно на старте и в serverless-модели.
Поэтому native-image не замена JVM, а другой режим деплоя. Долгоживущий бэкенд на сервере — JVM с разогревом. Функция, которая стартует по запросу и живёт секунды — native.
Когда это вообще критично
Не каждую миллисекунду старта стоит оптимизировать. Логика:
- Долгоживущий сервис (сервер, VM, контейнер, который живёт часами/днями). Старт в 5 секунд не имеет значения — он случается редко, а steady-state throughput важнее. Оптимизировать нет смысла.
- Масштабирование по нагрузке. Если поды добавляются под трафиком и каждый стартует 8 секунд — пользователи видят задержку на спайках. Тут ленивая инициализация и CDS уже дают эффект.
- Serverless / FaaS (Lambda, Cloud Run с scale-to-zero). Здесь старт = холодный старт функции, и 5 секунд — это провал. Вот тут native-image кардинально меняет картину.
Короткое summary
Старт Spring Boot складывается из classpath-сканирования, автоконфигурации, разогрева Hibernate, проксирования и JIT. Лечится в три приёма: lazy-initialization (бесплатно, ценой медленного первого запроса), CDS/AOT (15–30% без правок кода, требует настройки CI) и GraalVM native-image (старт за десятки миллисекунд, ценой ограничений на reflection и долгой сборки). Выбор уровня — по характеру деплоя: долгоживущему сервису ничего не нужно, serverless — native-image.
Что почитать
- Spring Boot Reference: Efficient Application Startup — раздел про
BufferingApplicationStartup, метрики старта. - Spring AOT Processing — как Spring 6 готовит приложение под native-image.
- GraalVM Native Image Reference Manual — ограничения reflection, ресурсы, динамических агентов.