我正在Docker容器内运行Java程序,该容器的硬盘内存限制为4GB。我将最大堆设置为3GB,但是Java程序仍然超出了限制并被杀死(OOMKilled)。
我的问题是:如何配置Java以遵守设置的容器限制并抛出OutOfMemoryException而不是尝试分配超出限制并被主机内核踢走?
>更新:我是一位经验丰富的Java开发人员,对JVM有一定的了解。我知道如何设置最大堆,但是我想知道是否有人知道设置JVM进程从操作系统中声明的总内存限制的方法。
答案 0 :(得分:4)
在容器内执行Java应用程序时,JVM人机工程学(负责根据主机的功能动态分配资源)不知道它在容器内运行,并计算出要使用的资源数量基于执行容器的主机的Java应用程序。鉴于此,您是否对容器设置限制都没有关系,JVM将以您主机的资源为基础进行计算。
在JDK 8u131 +和JDK 9中,有一个实验性的VM选项,该选项允许JVM人机工程学从CGgroup读取内存值。要启用它,必须将以下标志传递给JVM:
-XX:+ UnlockExperimentalVMOptions和-XX:+ UseCGroupMemoryLimitForHeap
如果启用这些标志,则JVM将知道该容器正在容器中运行,并将使JVM符合人体工程学,从而根据容器限制而不是主机的能力来计算应用程序的资源。
启用标志:
$ java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar app.jar
您可以使用ENV变量将JVM选项动态传递给您的容器。
示例:
运行您的应用程序的命令类似于:
$ java ${JAVA_OPTIONS} -jar app.jar
docker run 命令需要像这样传递ENV变量:
$ docker run -e JAVA_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" myJavaImage
希望这会有所帮助!
答案 1 :(得分:2)
除了Fabian Rivera的答案外,我还发现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')"
这将向您显示堆的大小,以MB为单位。要更改用于堆的总容器内存的百分比,请添加MaxRAMPercentage
参数,如下所示:
jrunscript -J-XX:MaxRAMPercentage=50 -e "print(Packages.java.lang.Runtime.getRuntime().maxMemory()/(1<<20) + 'M')"
现在,您可以尝试调整容器的大小和最大堆百分比了。