Production

The JVM in containers: UseContainerSupport, MaxRAMPercentage and a real memory budget

Production · Published 2026-10-24

Aerial view of a port terminal with rows of stacked shipping containers
A container limit is a hard wall: the heap is only one of the things that must fit inside it. Photo: rawpixel, CC0.

A JVM inside a container sees the container's limits, not the host's, as long as container support is active. Most problems come from what happens next: a default heap that is too small, or a heap that is too large for everything else the JVM needs. This guide covers detection, the percentage flags and how to build a budget that survives production.

Container detection

The java reference describes -XX:+UseContainerSupport as a Linux-only feature that lets the VM determine the amount of memory and the number of processors available to a process running in a container, and uses that information to size its resources. It is enabled by default and can be disabled with -XX:-UseContainerSupport.

Container support arrived in JDK 10 and was backported to later JDK 8 updates. Support for cgroup v2, which many current Linux distributions and Kubernetes nodes use, came later than cgroup v1 support. If you run an old JDK 8 or 11 update on a cgroup v2 host, check that the JVM sees the limits at all, as described below. Upgrading to a current update release is the reliable fix.

Check what the JVM sees

# Linux only: host or container configuration as the JVM sees it
$ java -XshowSettings:system -version

# detection details at startup
$ java -Xlog:os+container=info -version

# the heap size ergonomics chose
$ java -XX:+PrintFlagsFinal -version | grep -E 'MaxHeapSize|ActiveProcessorCount'

The reference describes -XshowSettings:system as showing the host system or container configuration (Linux only). Run these checks inside the same image and with the same limits as production; a laptop run proves nothing about a pod.

What the percentage flags do

OptionDefault (java reference)Effect
-XX:MaxRAMPercentage25 percentMaximum heap as a share of available memory
-XX:InitialRAMPercentage1.5625 percentInitial heap as a share of available memory
-XX:MinRAMPercentage50 percentMaximum heap for small heaps, which the reference describes as heaps of approximately 125 MB

The names mislead in two ways:

An explicit -Xmx overrides all of them. Choose one style per service: either a fixed -Xmx that you keep in sync with the container limit, or a percentage that follows the limit automatically.

Why the default is usually wrong for a service

With no heap options, the maximum heap is a quarter of the container's memory: a 2 GB pod gets a heap of about 512 MB. For a dedicated service container that wastes most of the memory you pay for. The usual fix is to raise MaxRAMPercentage, but how far depends on the non-heap footprint, which varies a lot between applications. With memory now one of the fastest-rising hardware costs, the budget below is also what decides whether a service fits the next size down; see the AI memory squeeze.

Building a memory budget

The container's limit must cover the whole process. A realistic budget has these lines:

ItemHow to size it
Heap-Xmx or MaxRAMPercentage; from the live set seen in GC logs plus headroom
MetaspaceSteady-state usage from jcmd VM.metaspace; cap with MaxMetaspaceSize (Metaspace)
Code cacheUp to ReservedCodeCacheSize, 240 MB by default with tiered compilation (code cache)
Thread stacksNumber of threads × stack size; the reference gives a 1024 KB default stack on Linux/x64
Direct buffersMaxDirectMemorySize, which otherwise defaults to the maximum heap size (direct memory)
GC, compiler, symbols, internalMeasure with Native Memory Tracking
Native libraries and allocator overheadThe gap between NMT's committed total and RSS

The direct buffer line deserves attention: if MaxDirectMemorySize is not set, the JVM allows direct buffers up to roughly the heap size, so a heap at 75% of the limit plus a burst of direct buffers can exceed the container's memory even though neither is "full".

The most reliable way to fill in the table is to run the service under production-like load with -XX:NativeMemoryTracking=summary, read committed values per category after warm-up and at peak, and compare with the container's own memory metrics.

Two kinds of out-of-memory

The second kind means the budget is wrong, not necessarily the heap. Lowering -Xmx or capping the non-heap areas explicitly makes failures happen inside the JVM, where they are diagnosable.

CPU limits

The JVM derives the number of available processors from the container's CPU settings and uses it to size GC threads, JIT compiler threads and the common ForkJoinPool, and to choose the default collector: per the ergonomics chapter, a machine with fewer than two processors or less than 1792 MB of memory gets the Serial collector instead of G1. A container limited to one CPU therefore runs Serial unless you choose otherwise; see choosing a collector.

When detection does not match what you want (for example, fractional CPU limits rounded down to one), override it. The reference documents -XX:ActiveProcessorCount for exactly this and notes that it is honoured even if container support is disabled:

-XX:ActiveProcessorCount=2

Check Runtime.getRuntime().availableProcessors() or the -XshowSettings:system output to see the result.

A starting point

JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=70 \
  -XX:MaxMetaspaceSize=256m \
  -XX:MaxDirectMemorySize=256m \
  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps \
  -XX:+ExitOnOutOfMemoryError \
  -Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=20m"

The percentages and caps are examples to replace with your measurements; what matters is that every large area has an explicit limit and that the total stays below the container limit. JAVA_TOOL_OPTIONS is read by the JVM itself, so it works even when the start command is buried in an image; the JVM prints a line noting that it picked the options up. /dumps and /logs should be mounted volumes, or the evidence disappears with the container.

Sources