-
Notifications
You must be signed in to change notification settings - Fork 11
Open
Description
Java 25 Performance Optimizations (AOT + Leyden + JVM Tuning)
Description
Leverage Java 25 features and Spring Boot 3.5 AOT for significant performance improvements: 50-75% faster startup, ~30% memory reduction, sub-millisecond GC pauses.
Changes
1. Enable Spring Boot AOT Processing
File: src/artifacts/api/pom.xml
Add execution to spring-boot-maven-plugin:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<id>process-aot</id>
<goals>
<goal>process-aot</goal>
</goals>
</execution>
</executions>
</plugin>2. Generate Project Leyden AOT Cache
File: src/artifacts/api/Dockerfile
After the existing jarmode=layertools extract step, add an intermediate stage to generate the AOT cache:
# After layertools extraction (existing code):
# COPY ${JAR_FILE} application.jar
# RUN java -Djarmode=layertools -jar application.jar extract
# Add new stage for AOT cache generation:
FROM eclipse-temurin:25-jre as aot-cache
WORKDIR /opt/app/bin
COPY --from=builder dependencies/ ./
COPY --from=builder snapshot-dependencies/ ./
COPY --from=builder spring-boot-loader/ ./
COPY --from=builder application/ ./
# Generate AOT cache with training run
RUN java -XX:AOTCacheOutput=/tmp/acl-service.aot \
-Dspring.context.exit=onRefresh \
org.springframework.boot.loader.launch.JarLauncher
# In final stage, copy the AOT cache:
COPY --from=aot-cache /tmp/acl-service.aot ./acl-service.aot3. Update JVM Options
File: src/artifacts/api/Dockerfile
Replace the current JAVA_OPTS environment variable:
ENV JAVA_OPTS="-XX:MaxRAMPercentage=80 \
-XshowSettings:system \
-XX:AOTCache=/opt/app/bin/acl-service.aot \
-Dspring.aot.enabled=true \
-XX:+UseCompactObjectHeaders \
-XX:+UseZGC \
-XX:+ZGenerational \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/tmp/heapdump.hprof \
-XX:+ExitOnOutOfMemoryError \
-XX:+UseContainerSupport \
-Djava.security.egd=file:/dev/./urandom"Expected Benefits
Startup Time
- 50-75% faster startup (Leyden AOT cache + Spring Boot AOT)
- Benchmarks: Spring PetClinic 3.4s → 0.8s (4x improvement)
- Multiplicative effect: 3x with Leyden alone, 4x when combined with Spring AOT
Memory Usage
- ~30% reduction from Compact Object Headers
- Reduced metaspace usage from AOT-generated code
- Better CPU cache utilization
Garbage Collection
- Sub-millisecond GC pauses with Generational ZGC
- 10% throughput improvement over single-gen ZGC
- No allocation stalls under load
JVM Warmup
- Immediate optimization with method profiling data
- JIT compiler uses pre-recorded profiles from training run
- Eliminates typical "warmup" period
Technical Details
Java 25 Features Used
- JEP 519: Compact Object Headers (production-ready)
- JEP 514: AOT Command-Line Ergonomics (production-ready)
- JEP 515: AOT Method Profiling (production-ready)
- JEP 483: AOT Class Loading & Linking (from JDK 24)
How It Works
- Build-time: Spring Boot AOT analyzes application and generates optimized initialization code
- Docker build: Training run creates Leyden AOT cache with class loading and method profiling data
- Runtime: JVM loads pre-computed data from AOT cache, skips expensive initialization
AOT Cache Lifecycle
- Generated during Docker image build (one-time per image)
- Embedded in the Docker image
- Automatically used by JVM when
-XX:AOTCacheflag is present - Must be regenerated when application code changes (automatic via Docker rebuild)
Implementation Notes
Prerequisites
- Java 25 runtime (already in use via eclipse-temurin:25-jre)
- Spring Boot 3.3+ (we're on 3.5.7)
- Maven build process
Build Time Impact
- AOT processing adds ~30-60 seconds to Maven build
- Docker image build adds ~10-20 seconds for training run
- Overall: acceptable for CI/CD pipelines
Image Size
- AOT cache file: ~10-50 MB (depends on application size)
- Spring AOT generated code increases JAR by ~10-20%
- Trade-off: slightly larger image for significantly better runtime performance
Compatibility
- All features are production-ready in Java 25
- No experimental flags required
- Works with existing Spring Boot 3.5 applications
- Compatible with GeoTools (JVM AOT doesn't affect runtime reflection)
Testing Strategy
Phase 1: Baseline Metrics
Before changes, capture:
- Application startup time
- Memory usage (heap, RSS)
- Response time percentiles (p50, p95, p99)
- Throughput (requests/sec)
Phase 2: Deploy and Measure
After implementation:
- Compare startup times (expect 50-75% improvement)
- Monitor memory usage (expect ~30% reduction)
- Verify GC pause times (expect <1ms)
- Load test to ensure no regression in throughput
Phase 3: Production Rollout
- Deploy to staging first
- Monitor for 1-2 weeks
- Gradual rollout to production
- Keep previous image tagged for quick rollback
Monitoring
Key metrics to track:
jvm.memory.used
jvm.memory.committed
jvm.gc.pause (should be <1ms)
application.started.time (startup duration)
http.server.requests (response times)
Rollback Plan
If issues occur:
- Revert to previous Docker image (without AOT)
- Remove
process-aotexecution from pom.xml - Remove AOT-related JVM flags from Dockerfile
- Rebuild and redeploy
No database migrations or code changes required for rollback.
References
- Project Leyden: https://openjdk.org/projects/leyden/
- JEP 514 (AOT Ergonomics): https://openjdk.org/jeps/514
- JEP 515 (Method Profiling): https://openjdk.org/jeps/515
- JEP 519 (Compact Object Headers): https://openjdk.org/jeps/519
- Spring Boot AOT: https://docs.spring.io/spring-boot/reference/packaging/aot.html
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels