Files
asmhatreandClaude Opus 5 a065696478 Add kubernetes-deployment: probes, shutdown, JVM ergonomics, HPA
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
2026-09-11 17:12:10 +00:00

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.cpu affects 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=75 is the usual correction; the manifests here set it through JAVA_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