06
Demo 06 — Low-Latency JVM: G1GC vs ZGC
Spring Boot 4.0.5 / Java 21
⏱ ~10 min
Run this demo
View source on GitHub ↗
cd demo-06-latency
chmod +x demo.sh
./demo.sh
Two identical Spring Boot apps, same heap, same load. One G1GC, one ZGC. The GC pause delta (not throughput) is the metric that matters for SLAs.
Metrics endpoint
Spring Boot exposes GC metrics via Micrometer + Actuator at /actuator/prometheus (not Quarkus’s /q/metrics):
# application.yml
management:
endpoints:
web:
exposure:
include: prometheus
prometheus:
metrics:
export:
enabled: true
UBI9 default GC
ubi9/openjdk-21-runtime ships Shenandoah by default. This demo overrides it explicitly:
JAVA_OPTS: "-XX:+UseG1GC" # container 1
JAVA_OPTS: "-XX:+UseZGC -XX:+ZGenerational" # container 2
ZGC throughput caveat
ZGC uses a load barrier — fires on every object read — adding ~5-15% constant overhead. In the hey load test, ZGC shows lower throughput than G1GC. This is expected and correct. The meaningful metric is the GC pause delta.
GC decision guide
| Collector | Pause | Choose when |
|---|---|---|
| Shenandoah | 1-20ms | UBI9 default — already this without any config |
| ZGC | < 1ms | p99 SLA tighter than 10ms, or heap > 32GB |
| G1GC | 50-300ms | Throughput-oriented, non-latency-sensitive |