06

Demo 06 — Low-Latency JVM: G1GC vs ZGC

Spring Boot 4.0.5 / Java 21 ⏱ ~10 min
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

Reference