JVM Memory in Docker: -Xmx vs Container Limits and OOMKilled
Fast recall
Quick revision
Core answer
Understanding of JVM memory model inside containers and the common OOMKilled production incident
Setting -Xmx to the container limit (e.g., -Xmx512m in a 512m container) — the JVM needs memory beyond heap for metaspace, threads, code cache, GC overhead
Explain the difference between heap and non-heap JVM memory, why MaxRAMPercentage of 75% is the standard recommendation, and how to use kubectl describe pod to diagnose OOMKilled
Interview-ready guide
Interview answer
Running a JVM inside a Docker container without explicit configuration causes the JVM to read total host memory rather than the container's memory limit, sizing its heap to 25% of the host RAM — on a 32 GB host, the JVM claims 8 GB of heap inside a container limited to 512 MB, and the kernel OOMKills it.
Container-aware heap sizing
- JDK 10+ respects cgroup memory limits with
-XX:+UseContainerSupportenabled by default — the JVM reads the container limit, not host RAM, and sets initial heap to 25% of the limit -XX:MaxRAMPercentage=75.0allocates 75% of the container limit to heap — leaving 25% for non-heap memory (Metaspace, thread stacks, direct buffers, the JVM itself); using 90%+ causes frequent GC pauses as the JVM runs up against the limit before OOMKill occurs- Verify the effective heap with
java -XX:+PrintFlagsFinal -version | grep MaxHeapSizeinside the container — compare the value to your Kubernetes resource limit to confirm they are consistent
The direct memory trap
- Non-heap memory is not managed by
-Xmx— Metaspace, thread stacks (1 MB per thread by default), and direct byte buffers (NIO, gRPC, Netty) all live outside the heap limit - A Spring Boot app under load with 50 active threads consumes 50 MB of stack alone; a Netty-based WebFlux app allocates significant direct memory; set the container limit to at least twice the configured heap to leave room for non-heap allocation
-XX:MaxDirectMemorySizelimits direct buffer allocation; without it, direct memory allocation is unbounded and the total process footprint can far exceed the heap limit
Diagnostic and monitoring
- When a container is OOMKilled,
kubectl describe podshowsOOMKilledin the last state with an exit code of 137 — the kill came from the container runtime, not a JVM OutOfMemoryError; the JVM had no warning - Monitor RSS (resident set size) of the Java process with
kubectl top pod, not just heap usage — RSS includes heap plus all non-heap memory and is what the kernel counts against the cgroup limit
JVM Memory in Docker: -Xmx vs Container Limits and OOMKilled
Direct Answer
Before JDK 11, the JVM didn't respect cgroup memory limits — it sized the heap based on total host RAM, causing OOMKilled when the container exceeded its limit. JDK 11+ with -XX:+UseContainerSupport (default) reads the cgroup limit correctly. Use -XX:MaxRAMPercentage=75.0 instead of fixed -Xmx to size heap as a percentage of container memory.
Common Mistakes & Pitfalls
Setting -Xmx to the container limit (e.g., -Xmx512m in a 512m container) — the JVM needs memory beyond heap for metaspace, threads, code cache, GC overhead
Check your understanding
Answer these without looking back at the article.
- What is the difference between -Xmx and -XX:MaxRAMPercentage?
- How do you diagnose an OOMKilled container in Kubernetes?
- What is metaspace and why can it cause OutOfMemoryError even when heap is available?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.