Как предотвратить превышение лимита памяти в Java? - PullRequest
0 голосов
/ 27 августа 2018

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

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

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

Ответы [ 2 ]

0 голосов
/ 04 сентября 2018

В дополнение к ответу Фабиана Риверы я обнаружил, что 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')"

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

0 голосов
/ 27 августа 2018

Когда приложение 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

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

...