One of the most expensive one-character bugs in Kubernetes is writing M where you meant Mi. They look almost the same, but 512M and 512Mi are different amounts of memory — and the gap is exactly what gets pods OOMKilled in production. This guide explains the two unit systems, proves the difference on a real cluster, and shows you how to stop the mistake from ever shipping.
Kubernetes memory suffixes come in two flavours: decimal SI (k, M, G, T = powers of 1000) and binary IEC (Ki, Mi, Gi, Ti = powers of 1024). The kernel, cgroups, and container runtimes all count in binary, so you should always use Mi/Gi.
| You wrote | System | Bytes | vs the binary form |
|---|---|---|---|
512M |
decimal (SI) | 512,000,000 | — |
512Mi |
binary (IEC) | 536,870,912 | +24 MB |
4G |
decimal (SI) | 4,000,000,000 | — |
4Gi |
binary (IEC) | 4,294,967,296 | +295 MB (~7%) |
4G is only about 93% of 4Gi. That ~280–295 MB shortfall is the difference between "runs fine" and "OOMKilled at peak load."
The base conversions worth memorizing:
1Ki = 1,024 bytes | 1k = 1,000 bytes1Mi = 1,048,576 bytes | 1M = 1,000,000 bytes1Gi = 1,073,741,824 bytes | 1G = 1,000,000,000 bytes| You need | Why |
|---|---|
| A Kubernetes cluster (minikube/kind is fine) | To observe the cgroup limit for real |
kubectl |
To apply manifests and exec into pods |
| Five minutes | The demo is small |
Apply a pod that requests exactly 512M (decimal) and inspect the cgroup limit the kernel actually enforces.
kubectl run mem-decimal --image=busybox --restart=Never \
--overrides='{"spec":{"containers":[{"name":"c","image":"busybox","command":["sleep","3600"],"resources":{"limits":{"memory":"512M"}}}]}}'
# cgroup v2: the enforced limit, in bytes
kubectl exec mem-decimal -- cat /sys/fs/cgroup/memory.max
# 512000000 <- exactly 512 * 1,000,000 (decimal)
Now the binary version:
kubectl run mem-binary --image=busybox --restart=Never \
--overrides='{"spec":{"containers":[{"name":"c","image":"busybox","command":["sleep","3600"],"resources":{"limits":{"memory":"512Mi"}}}]}}'
kubectl exec mem-binary -- cat /sys/fs/cgroup/memory.max
# 536870912 <- exactly 512 * 1,048,576 (binary)
On a cgroup v1 node the file is
/sys/fs/cgroup/memory/memory.limit_in_bytesinstead. Either way, the number is the exact byte count Kubernetes derived from your suffix — and the kernel OOM killer enforces that number, not the "512" you eyeballed.
Clean up:
kubectl delete pod mem-decimal mem-binary
Give a pod a 4M limit (tiny, decimal) and let it try to allocate more. The kernel kills it.
kubectl run oom-demo --image=polinux/stress --restart=Never \
--overrides='{"spec":{"containers":[{"name":"c","image":"polinux/stress","resources":{"limits":{"memory":"4M"}},"command":["stress","--vm","1","--vm-bytes","20M","--vm-hang","1"]}}]}}'
kubectl get pod oom-demo -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}{"\n"}'
# OOMKilled
kubectl get pod oom-demo -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}{"\n"}'
# 137 <- 128 + SIGKILL(9); the signature of an OOM kill
The lesson scales up: an app tuned for 4 GiB but handed a 4G limit sits ~295 MB over its real ceiling and meets the same fate under load.
kubectl delete pod oom-demo
Runtimes that size themselves in binary units collide with decimal pod limits:
-Xmx4g as 4 GiB (4,294,967,296 bytes) — for the heap alone, before metaspace, thread stacks, and off-heap buffers.4G is 4,000,000,000 bytes — smaller than the heap max.Result: the heap grows past the container limit and the JVM is OOMKilled. Two fixes:
# Option A — binary limit with headroom for non-heap memory
resources:
limits:
memory: "5Gi"
env:
- name: JAVA_TOOL_OPTIONS
value: "-Xmx4g"
# Option B — let the JVM size the heap from the cgroup limit (single source of truth)
resources:
limits:
memory: "4Gi"
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=75.0"
Option B is usually best: set the pod limit (in Gi) and let MaxRAMPercentage derive the heap, so there is only one number to get right.
Find decimal memory units across running pods:
# Any memory value ending in a bare M/G/T (no trailing "i")
kubectl get pods -A -o yaml | grep -iE 'memory: *"?[0-9]+[MGT]([^i]|$)'
Do the same across your Helm charts and kustomize bases in Git:
grep -rnE 'memory: *"?[0-9]+[MGT]([^i]|$)' ./charts ./manifests
Detection is good; prevention is better. Add an admission policy so decimal memory units can never reach the cluster. A Kyverno rule:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-binary-memory-units
spec:
validationFailureAction: Enforce
rules:
- name: memory-must-be-binary
match:
any:
- resources:
kinds: ["Pod"]
validate:
message: "Memory requests/limits must use binary units (Ki/Mi/Gi/Ti)."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*i" # value must end in 'i'
Wire the same check into CI (e.g. conftest/OPA against your rendered manifests) so pull requests fail before merge, and bake Mi/Gi defaults into your shared Helm charts so app teams inherit the right units.
Don't over-apply this rule. CPU uses m for millicores — a decimal fraction of a core, with no binary variant:
resources:
requests:
cpu: "250m" # 0.25 of a core
memory: "256Mi" # binary bytes
limits:
cpu: "1" # 1 core, identical to 1000m
memory: "512Mi"
cpu: 1000m and cpu: "1" are the same. memory: 512M and memory: 512Mi are not. Keep the two mental models separate: millicores for CPU, binary Mi/Gi for memory.
M/G for memory. Almost always wrong — use Mi/Gi.4G ≈ 4Gi. It's ~7% (280–295 MB) short, right where OOMKills happen.-Xmx4g (binary) under a 4G (decimal) limit is a guaranteed OOM.256M and 256Mi limits and diff their /sys/fs/cgroup/memory.max values. What's the exact difference?OOMKilled reason and exit code 137.M/G values?memory: 512M. What does admission say?Self-check:
1Gi? How many is 1G? What's the percentage gap?4G limit OOMKill an app sized for 4 GiB?cpu: 500m affected by the MB-vs-MiB issue? Why not?The takeaway is one character wide: use Mi and Gi for memory. Your intent then matches exactly how the kernel, cgroups, and runtimes count — and the OOMKiller has nothing to surprise you with.
Learned the concepts? Test yourself with Kubernetes interview questions.
Go to the question bank →