JVM Heap Size Calculator — Xms, Xmx and MaxRAMPercentage

JVM Heap Size Calculator

Work out sane -Xms / -Xmx values from your container or host memory, or use the container-aware -XX:MaxRAMPercentage alternative instead.

Available memory

Fixed heap (classic)

Container-aware (Java 10+)

Notes

How to use this tool

  1. Enter the total memory available to the JVM — the container’s memory limit, or the host’s RAM if it’s not containerized.
  2. Set the reserve percentage for everything that is not JVM heap: the OS, sidecars, other processes sharing the box, and the JVM’s own off-heap overhead.
  3. Use the Fixed heap panel if you set -Xms/-Xmx explicitly — this is the traditional approach and still the most predictable.
  4. Use the Container-aware panel instead if you’d rather let the JVM compute its own heap size from the container’s actual memory limit at startup.
  5. Pick one approach, not both — setting -Xmx and -XX:MaxRAMPercentage together is redundant, since an explicit -Xmx always wins.

Getting JVM heap sizing wrong is one of the most common causes of a Java service getting OOM-killed by its container orchestrator, and it happens for a specific, avoidable reason: the heap is only part of a JVM process’s total memory footprint, but it is easy to size -Xmx as if it were the whole footprint. On top of the heap, a running JVM needs metaspace (class metadata), thread stacks (one per thread, several hundred KB to a few MB each), the JIT compiler’s code cache, direct and native memory buffers, and garbage collector bookkeeping structures. None of that is covered by -Xmx. Set the heap to 100% of the container’s memory limit and the process will eventually be killed for exceeding it, heap usage looking perfectly healthy the whole time.

Setting -Xms (initial heap) equal to -Xmx (maximum heap) is standard practice in containers, for a reason that matters more there than on a long-lived physical server: heap resizing is a stop-the-world event in most collectors, and containers are usually short-lived, request-driven, or autoscaled, so there is little benefit to starting small and growing — you just pay the resize pause during the workload’s early, often most latency-sensitive traffic. Pre-allocating the full heap at startup trades a slightly slower cold start for a completely flat, predictable memory profile afterward.

The container-aware alternative, -XX:MaxRAMPercentage, exists because a fixed -Xmx in megabytes has to be manually recalculated every time the container’s memory limit changes — a common source of drift between a Kubernetes manifest’s resources.limits.memory and the JVM flags baked into an image. Since JDK 10 (and backported to 8u191), the JVM reads the cgroup memory limit automatically when -XX:+UseContainerSupport is active, which it is by default, and MaxRAMPercentage expresses the heap as a percentage of whatever that limit turns out to be. Bump the pod’s memory limit in a manifest and the heap scales with it automatically, with no image rebuild.

There are two related, lesser-known flags worth knowing: -XX:InitialRAMPercentage sets the starting heap as a percentage (the percentage equivalent of -Xms), and -XX:MinRAMPercentage only takes effect for very small heaps (under about 200MB), where the JVM switches to a different, more conservative default calculation. For anything above that threshold, MaxRAMPercentage is the one that matters. Note also that an explicit -Xmx always overrides MaxRAMPercentage if both are present — they are alternative ways of arriving at the same number, not additive settings.

A reserve of 20-25% for non-heap overhead is a reasonable starting point for a typical service, but it is a starting point, not a rule. Thread-heavy applications (large connection pools, many parallel workers) need more headroom for stack space; anything doing significant reflection, dynamic class generation, or running many microservices’ worth of dependencies in one process needs more metaspace; and GC algorithms differ in how much working memory they want beyond the logical heap size, with G1 and ZGC generally wanting more slack than the older Parallel collector. Watch actual resident memory (RSS) in production and adjust the reserve percentage from there rather than trusting a single default forever.

Frequently asked questions

Why is my Java container getting OOM-killed even though heap usage looks fine?

Because -Xmx only bounds the heap, not the JVM’s total memory use. Metaspace, thread stacks, the JIT code cache and native/direct buffers all sit outside the heap and count against the container’s memory limit. If -Xmx is set too close to that limit, the process gets killed by non-heap growth even while the heap itself stays well under its cap.

Should I set -Xms equal to -Xmx?

In a container, generally yes. Heap resizing is a stop-the-world pause in most collectors, and containers rarely benefit from starting small and growing, since they tend to be short-lived or autoscaled. Pre-allocating the full heap avoids that pause entirely at the cost of a slightly slower start.

What does -XX:MaxRAMPercentage actually do?

It sets the maximum heap as a percentage of the container’s detected memory limit rather than a fixed megabyte value, so the heap automatically tracks the container’s actual memory allocation. It requires container support to be active, which it is by default since JDK 10 (and on 8u191 and later).

Should I use -Xmx or -XX:MaxRAMPercentage?

Pick one. An explicit -Xmx always takes precedence if both are set, so combining them is redundant. -Xmx is more predictable for a fixed-size deployment; MaxRAMPercentage is less maintenance when the container’s memory limit changes across environments or over time.

What is a safe default reserve percentage for non-heap memory?

20-25% covers most typical services. Increase it for thread-heavy applications, anything with heavy reflection or many loaded classes (more metaspace), or collectors like G1 and ZGC that want more working memory outside the heap than the older Parallel collector does. Confirm with real RSS measurements in production rather than trusting a default indefinitely.