A JVM service runs with `-Xmx4g` but its pod limit is `4G`. What happens, and how do you fix it?
Quick Answer
The JVM `g` is binary: `-Xmx4g` = 4 GiB = 4,294,967,296 bytes for the heap alone. The pod `4G` limit is decimal = 4,000,000,000 bytes — smaller than the heap max, before you even count metaspace, thread stacks, and off-heap. As the heap grows the container is OOMKilled. Fix: set the limit to `4Gi` (and leave headroom above `-Xmx` for non-heap memory).
Detailed Answer
This bug bites because two tools use two unit conventions for the same letter. The JVM treats g/m/k as binary (GiB/MiB/KiB), so -Xmx4g reserves up to 4,294,967,296 bytes for the heap. Kubernetes treats 4G as decimal, 4,000,000,000 bytes. So the container limit is ~295 MB *below* the JVM's max heap — and total JVM memory (heap + metaspace + code cache + thread stacks + direct buffers) is larger still.
Fixes, best first
- Set the pod limit in binary units with headroom: e.g. memory: 5Gi for -Xmx4g, so non-heap memory fits under the cap. - Prefer container-aware sizing: drop the fixed -Xmx and use -XX:MaxRAMPercentage=75 so the JVM sizes the heap from the cgroup limit (which is then the single source of truth — just make sure that limit is in Gi). - Keep app memory settings and pod limits in the same units to avoid this class of bug entirely.
Code Example
# Container-aware, unit-safe sizing
resources:
limits:
memory: "5Gi" # heap + metaspace + stacks + off-heap
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=75.0" # heap sized from the cgroup limitInterview Tip
Call out that the JVM `g` is already binary, so the mismatch is decimal-limit vs binary-heap — and that non-heap memory needs headroom above -Xmx.