Gymterview
senior

Как Docker взаимодействует с JVM? Какие есть нюансы?

Взаимодействие Docker с JVM — это набор нюансов, связанных с тем, как JVM определяет доступные ресурсы (память, CPU) в условиях ограничений cgroups контейнера.

1. Осведомлённость JVM о лимитах контейнера

До Java 10 JVM не знала о cgroups и определяла доступную память и CPU по хосту, а не по контейнеру. Это приводило к OOM-kill, когда JVM пыталась использовать больше памяти, чем разрешено контейнеру.

Начиная с Java 10+ (и бэкпорт в 8u191), флаг -XX:+UseContainerSupport включён по умолчанию, и JVM корректно определяет лимиты контейнера.

2. Настройка памяти

Пример
# Рекомендуемый подход — процент от памяти контейнера
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

Почему 75%, а не 100%? JVM использует память не только для heap:

  • Heap (основная память для объектов)
  • Metaspace (метаданные классов)
  • Thread stacks (стек каждого потока)
  • Direct memory (NIO буферы)
  • Code cache (JIT-скомпилированный код)
  • GC overhead

3. Настройка CPU

JVM определяет количество доступных процессоров из cgroups. Это влияет на:

  • Количество потоков GC
  • Размер пула ForkJoinPool.commonPool()
  • Количество потоков в parallelStream()
Пример
# Ограничение CPU
docker run --cpus=2.0 myapp

4. Graceful shutdown

Для корректного завершения Spring Boot приложения важно, чтобы Java-процесс получал сигнал SIGTERM:

Пример
# ПРАВИЛЬНО: exec-форма — java получает PID 1
ENTRYPOINT ["java", "-jar", "app.jar"]

# НЕПРАВИЛЬНО: shell-форма — SIGTERM получает shell, не java
ENTRYPOINT java -jar app.jar

В Spring Boot включите graceful shutdown:

Пример
# application.yml
server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

5. Время запуска

Для ускорения запуска в контейнере:

  • Используйте -Djava.security.egd=file:/dev/./urandom
  • Рассмотрите CDS (Class Data Sharing) или Spring AOT
  • Используйте GraalVM Native Image для сокращения времени старта до миллисекунд

На собеседовании: это вопрос senior-уровня. Три ключевых нюанса: (1) UseContainerSupport — JVM должна знать о лимитах контейнера (до Java 10 не знала), (2) MaxRAMPercentage=75% — heap это не вся память JVM, (3) exec-форма ENTRYPOINT для получения SIGTERM. Знание этих деталей показывает реальный production-опыт запуска Java в Docker.