Кейс: под убивали OOMKilled, хотя heap был в норме

Сервис в Kubernetes падал по OOMKilled раз в несколько часов под нагрузкой. Heap dump снимали заранее по алерту — occupancy heap держался на трети от -Xmx, GC логи спокойные, full GC не было. По логике JVM памяти хватало, а под всё равно убивали.

Смотрели не туда: -Xmx ограничивает только heap. JVM ещё держит metaspace, стеки потоков, direct buffers для NIO, code cache JIT-компилятора и структуры GC — это native memory вне heap, а cgroup limit считает суммарный RSS процесса, а не то, что видно в heap dump.

RSS процесса был почти в лимите контейнера, пока heap оставался свободным. -Xmx стоял почти равным лимиту cgroup, без запаса на всё остальное — первый всплеск direct buffers при большом батче добивал RSS до предела, и kubelet убивал под.

-XX:+UseContainerSupport включён по умолчанию с JDK 10+ и правильно видит cgroup limit. Это не спасает от переполнения: JVM всё равно позволит heap занять почти весь видимый лимит, если не ограничить его явно через -XX:MaxRAMPercentage или запас в -Xmx.

Финал: -Xmx описывает лимит heap, а OOMKilled считает лимит всего процесса.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки