如何防止Java超出容器内存限制?

Gee*_*ing 5 java containers memory-limit cgroups docker

我在硬内存限制为 4GB 的 Docker 容器中运行 Java 程序。我已将最大堆设置为 3GB,但 Java 程序仍然超出限制并被杀死(OOMKilled)。

我的问题是:如何配置 Java 以遵守设置的容器限制并抛出 OutOfMemoryException 而不是尝试分配超出限制并让主机内核踢它的屁股?

更新:我是一名经验丰富的 Java 开发人员,对 JVM 有一定的了解。我知道如何设置最大堆,但我想知道是否有人知道设置JVM 进程从操作系统声明的内存限制的方法。

Fab*_*era 7

当 Java 应用程序在容器内执行时,JVM 人体工程学(负责根据主机的能力动态分配资源)不知道它正在容器内运行,它会计算 Java 应用程序要使用的资源数量基于正在执行容器的主机。鉴于此,您是否对容器设置限制并不重要,JVM 将把您主机的资源作为进行该计算的基础。

从 JDK 8u131+ 和 JDK 9 开始,有一个实验性的 VM 选项,允许 JVM 人体工程学从 CGgroups 读取内存值。要启用它,您必须将以下标志传递给 JVM:

-XX:+UnlockExperimentalVMOptions 和 -XX:+UseCGroupMemoryLimitForHeap

如果您启用这些标志,JVM 将知道它正在容器内运行,并使 JVM 符合人体工程学,以根据容器限制而不是主机的功能来计算应用程序的资源。

启用标志:

$ java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -jar app.jar
Run Code Online (Sandbox Code Playgroud)

您可以使用 ENV 变量将 JVM 选项动态传递给您的容器。

例子:

运行您的应用程序的命令类似于:

 $ java ${JAVA_OPTIONS} -jar app.jar
Run Code Online (Sandbox Code Playgroud)

泊坞窗运行命令需要通过ENV变量是这样的:

$ docker run -e JAVA_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap" myJavaImage
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!


Gee*_*ing 5

除了 Fabian Rivera 的回答之外,我还发现 Java 10 对在容器中运行没有任何自定义启动参数有很好的支持。默认情况下,它使用 25% 的容器内存作为堆,这对于某些用户来说可能有点低。您可以使用以下参数更改此设置:

-XX:MaxRAMPercentage=50
Run Code Online (Sandbox Code Playgroud)

要使用 Java 10,请运行以下 docker 命令:

docker run -it --rm -m1g --entrypoint bash openjdk:10-jdk
Run Code Online (Sandbox Code Playgroud)

它将为您提供一个 bash 环境,您可以在其中运行 JDK 中的可执行文件。例如,要运行一小段脚本,您可以jrunscript像这样使用:

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

这将显示堆的大小(以 MB 为单位)。要更改用于堆的总容器内存的百分比,请添加MaxRAMPercentage如下参数:

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

现在您可以调整容器的大小和堆的最大百分比。