Companion code for "Deploying Spring Boot 4 on Kubernetes: Probes, Graceful Shutdown, Limits and JVM Ergonomics". A dependency outage under three probe-group setups, a rolling restart under load four ways (three runs each), the JVM's ergonomic choices for nine pod shapes, one GC-heavy load under five CPU limits with throttling counters, and an HPA driven by a Micrometer gauge through prometheus-adapter. Measured on k3s v1.36.4. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01C3TETMrqVUWeFkNtz3Jbo3
3.3 KiB
3.3 KiB
3. JVM ergonomics: what the JVM decides from your pod spec
← 2. Probes · Index · Next: 4. CPU limits and GC →
With no JVM options at all, the JVM reads the container's cgroup limits and picks a processor
count, a garbage collector, a heap size and thread counts. demo-ergonomics.sh
runs java -XX:+PrintFlagsFinal -version in a pod per resource shape
(jvm-ergonomics.txt):
cpu.req cpu.lim mem.req mem.lim | CPUs GC MaxHeap PGCThr CGCThr JITThr
100m none 256Mi 2Gi | 2 G1GC 512M 2 1 2
100m 500m 256Mi 512Mi | 1 SerialGC 128M 0 0 2
100m 1 256Mi 1Gi | 1 SerialGC 256M 0 0 2
100m 1 256Mi 2Gi | 1 SerialGC 512M 0 0 2
100m 1500m 256Mi 2Gi | 2 G1GC 512M 2 1 2
100m 2 256Mi 1Gi | 2 SerialGC 256M 0 0 2
100m 2 256Mi 1700Mi | 2 SerialGC 426M 0 0 2
100m 2 256Mi 1800Mi | 2 G1GC 450M 2 1 2
100m 2 256Mi 4Gi | 2 G1GC 1024M 2 1 2
The rules the table follows
- CPUs = the limit, rounded up. 500m → 1, 1500m → 2. With no limit, the JVM sees every CPU on the node - here 2, on a production node perhaps 64.
- Requests are ignored. Every row has a 100m request. JDK 19 removed CPU shares from the
calculation (JDK-8281181), so on a modern JDK
requests.cpuaffects scheduling and nothing the JVM decides. - G1 needs 2 CPUs and about 1792 MB. Below either, the JVM is not a "server-class machine" and picks SerialGC: a single-threaded, stop-the-world collector. 2 CPUs with 1700Mi → Serial; 1800Mi → G1. A typical Spring Boot pod sized 512Mi-1Gi therefore runs SerialGC without anyone having chosen it.
- Heap = 25 % of the memory limit (
MaxRAMPercentage=25). A 512Mi pod gets a 128 MB heap and 384 MB for metaspace, code cache, thread stacks and native memory - usually far more than they use.-XX:MaxRAMPercentage=75is the usual correction; the manifests here set it throughJAVA_TOOL_OPTIONS. - 1500m gives G1 two parallel GC threads on one and a half CPUs of quota. That combination is what chapter 4 measures.
cgroup v1 here, v2 in production
The lab node is cgroup v1 (a limitation of the VM it ran in; Kubernetes 1.35+ refuses v1 unless
failCgroupV1=false). JVM container detection works the same on both. The files differ:
| cgroup v1 | cgroup v2 | |
|---|---|---|
| CPU limit | cpu/cpu.cfs_quota_us / cpu.cfs_period_us |
cpu.max (150000 100000) |
| throttling | cpu/cpu.stat: nr_throttled, throttled_time (ns) |
cpu.stat: nr_throttled, throttled_usec |
| memory limit | memory/memory.limit_in_bytes |
memory.max |
JvmController reads either.
java -XshowSettings:system -version prints what the JVM detected, including the provider:
Operating System Metrics:
Provider: cgroupv1
Effective CPU Count: 2
CPU Period: 100000us
CPU Quota: 150000us