Как предотвратить превышение Java ограничениями памяти контейнера?

Я запускаю программу Java внутри контейнера Docker с жестким ограничением памяти 4 ГБ. Я установил максимальную кучу на 3 ГБ, но все же программа Java превышает лимит и погибает (OOMKilled).

У меня вопрос: как я могу настроить Java на соблюдение установленного ограничения контейнера и выбросить исключение OutOfMemoryException вместо того, чтобы пытаться выделить сверх лимита и получить задницу от ядра хоста?

Обновление: я опытный разработчик Java и хорошо разбираюсь в JVM. Я знаю, как установить максимальную кучу, но мне интересно, знает ли кто-нибудь способ установить ограничение для общей памяти, которую процесс JVM требует от ОС.


person Geert Schuring    schedule 27.08.2018    source источник
comment
Можете ли вы поделиться полной командой, которую вы используете для запуска JVM?   -  person BackSlash    schedule 27.08.2018
comment
Я только установил максимальную кучу на 3 ГБ.   -  person Geert Schuring    schedule 27.08.2018
comment
@GeertSchuring Нам нужно увидеть, как вы это делаете. Пожалуйста, поделитесь полной командой   -  person BackSlash    schedule 27.08.2018
comment
Ну, это вызывает Error, и маловероятно, что вы сможете снова встать на ноги после OOME. Чтобы JVM не использовала слишком много памяти, вам нужно уменьшить потребление памяти в вашем приложении или вам нужно обновить контейнер, выделив больше памяти.   -  person AxelH    schedule 27.08.2018
comment
Не объясняйте, что вы думаете о том, что сделали. Запишите точную командную строку, которую вы используете (и, возможно, добавьте точную версию JRE, которую вы используете)   -  person GhostCat    schedule 27.08.2018
comment
Это команда, которая используется Docker для запуска приложения: java -Xmx3g -jar application.jar   -  person Geert Schuring    schedule 27.08.2018
comment
Я проясню вопросы, так как заметил, что это непонятно.   -  person Geert Schuring    schedule 27.08.2018
comment
Измените свой вопрос, включив в него консольную команду java. Обновления в комментариях не так заметны для будущих читателей. Спасибо!   -  person halfer    schedule 27.08.2018


Ответы (2)


Когда приложение Java выполняется внутри контейнера, эргономика JVM (которая отвечает за динамическое назначение ресурсов на основе возможностей хоста) не знает, что оно выполняется внутри контейнера, и вычисляет количество ресурсов, которые будут использоваться приложением Java. на основе хоста, на котором выполняется ваш контейнер. Учитывая это, не имеет значения, устанавливаете ли вы ограничения для своего контейнера, JVM будет использовать ресурсы вашего хоста в качестве основы для выполнения этого расчета.

В JDK 8u131 + и JDK 9 есть экспериментальная опция виртуальной машины, которая позволяет эргономике JVM считывать значения памяти из групп CG. Чтобы включить его, вы должны передать JVM следующие флаги:

-XX: + UnlockExperimentalVMOptions и -XX: + UseCGroupMemoryLimitForHeap

Если вы включите эти флаги, JVM будет знать, что выполняется внутри контейнера, и сделает эргономику JVM для расчета ресурсов приложения на основе ограничений контейнера, а не возможностей хоста.

Включение флагов:

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar app.jar

Вы можете динамически передавать параметры JVM своему контейнеру с помощью переменных ENV.

Пример:

Команда для запуска вашего приложения должна выглядеть примерно так:

 $ java ${JAVA_OPTIONS} -jar app.jar

И команда docker run должна передать переменную ENV следующим образом:

$ docker run -e JAVA_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" myJavaImage

Надеюсь это поможет!

person Fabian Rivera    schedule 27.08.2018
comment
хорошо обработано и объяснено. it does not matter if you set limits to your container, the JVM will take your host's resources as the base for doing that calculation. понял. (1) Но разве это не превосходит всю суть пространства имен и cgroups. (2) из ​​вики - cgroup namespace type has existed since March 2016 in Linux 4.6. Итак, в чем смысл этого нового пространства имен cgroup. Почему приложения, например, JVM, должны заботиться о добавлении поддержки контейнеров - движок докеров, то есть containerd, должен делать это по умолчанию. Немного сложно осмыслить это ???? - person Vamsh; 10.10.2020

В дополнение к ответу Фабиана Риверы я обнаружил, что Java 10 имеет хорошую поддержку для работы в контейнерах без каких-либо настраиваемых параметров запуска. По умолчанию он использует 25% памяти контейнеров как кучу, что может быть немного мало для некоторых пользователей. Вы можете изменить это с помощью следующего параметра:

-XX:MaxRAMPercentage=50

Чтобы поиграть с Java 10, выполните следующую команду docker:

docker run -it --rm -m1g --entrypoint bash openjdk:10-jdk

Это даст вам среду bash, в которой вы можете запускать исполняемые файлы из JDK. Например, для запуска небольшого фрагмента скрипта вы можете использовать jrunscript следующим образом:

jrunscript -e "print(Packages.java.lang.Runtime.getRuntime().maxMemory()/(1<<20) + 'M')"

Это покажет вам размер кучи в МБ. Чтобы изменить процентную долю общей памяти контейнера, которая используется для кучи, добавьте параметр MaxRAMPercentage следующим образом:

jrunscript -J-XX:MaxRAMPercentage=50 -e "print(Packages.java.lang.Runtime.getRuntime().maxMemory()/(1<<20) + 'M')"

Теперь вы можете поэкспериментировать с размером контейнера и максимальным процентом кучи.

person Geert Schuring    schedule 04.09.2018