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

How to prevent Java from exceeding the container memory limits?

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

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

更新:我是一名经验丰富的 Java 开发人员,对 JVM 有一定的了解。我知道如何设置最大堆,但我想知道是否有人知道一种方法可以限制 JVM 进程从 OS 请求的 内存。

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

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

-XX:+UnlockExperimentalVMOptions and -XX:+UseCGroupMemoryLimitForHeap

如果您启用这些标志,JVM 将意识到 运行在容器内,并使 JVM 人体工程学根据容器限制而不是主机的能力来计算应用程序的资源。

启用标志:

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

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

示例:

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

 $ java ${JAVA_OPTIONS} -jar app.jar

docker运行命令需要像这样传递ENV变量:

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

希望对您有所帮助!

除了 Fabian Rivera 的回答之外,我发现 Java 10 对容器中的 运行ning 有很好的支持,无需任何自定义启动参数。默认情况下,它使用 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')"

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