如何检查 Docker 容器中的应用程序是否忽略 Java 内存选项?
How to check whether an app in Docker container ignores Java memory options?
有一个 Java 11 (SpringBoot 2.5.1) 应用程序,工作流程简单:
- 上传档案(作为每个大小为 50-100 Mb 的多部分文件)
- 在内存中解压它们
- 通过 JMS 将每个解压缩的文件作为消息发送到队列
当我 运行 在本地 java -jar app.jar
应用程序时,它的内存使用情况(在 VisualVM 中)看起来像锯子:稳定基线(~ 100 Mb)上的峰值(~ 400 Mb)。
当我 运行 同一个应用程序在 Docker 容器中时,内存消耗增长到 700 Mb 甚至更高,直到出现 OutOfMemoryError。 GC 似乎根本不起作用。即使内存选项存在 (java -Xms400m -Xmx400m -jar app.jar
),容器似乎完全忽略它们仍然消耗更多内存。
因此容器中的行为与 OS 中的行为截然不同。
我在 DockerDesktop Windows 10
和 OpenShift 4.6
中尝试了这张 Docker 图片,得到了两张相似的内存使用图片。
Docker文件
FROM bellsoft/liberica-openjdk-alpine:11.0.9-12
RUN addgroup -S apprunner && adduser -S apprunner -G apprunner
COPY target/app.jar /home/apprunner/app.jar
USER apprunner:apprunner
WORKDIR /home/apprunner
EXPOSE 8080
ENTRYPOINT java -Xms400m -Xmx400m -jar app.jar
Java 版本
# HOST
java -version
java 11.0.10 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)
# CONTAINER
java -version
openjdk version "11.0.9" 2020-10-20 LTS
OpenJDK Runtime Environment (build 11.0.9+12-LTS)
OpenJDK 64-Bit Server VM (build 11.0.9+12-LTS, mixed mode)
会不会是 liberica-openjdk v11.0.9-12
中有关闭 GC 之类的“特殊设置”?
请帮我弄清楚这种情况出了什么问题,以及如何使应用程序在容器内外的行为方式相同?
更新:
JVM 按预期工作,永远不会超过内存限制。实际上问题在于容器行为:
- 它产生了 200Mb 的开销;
- 它从不释放内存。
当(-Xmx=500m 且容器内存限制为 600Mb)时,#(1) 导致应用程序在 OpenShift 集群中出现 OutOfMemory 错误。所以,事实上,只有 400Mb=(600-200) 可用于 JVM。
#(2) 给人的印象是 GC 不起作用,尽管它确实起作用了:已用内存大小的定期日志记录表明,在增长到峰值(~400Mb)后,它的水平 returns 回到(到~100Mb),同时整体容器内存保持在最高水平(最小减少~20Mb)。
所以这种情况下的解决方案是考虑容器开销并将 OpenShift 容器允许的内存限制设置为 800Mb。
在Java11中,您可以通过在JVM选项中添加-XX:+PrintCommandLineFlags
来找出已传递给JVM的标志以及JVM设置的“符合人体工程学”的标志.
这应该会告诉您您正在使用的容器是否覆盖了您提供的标志。
话虽如此,(IMO) 容器不太可能覆盖参数。
JVM 使用比 -Xmx
选项更多的内存并不罕见。解释是该选项仅控制 Java 堆的大小。 JVM 消耗大量不属于 Java 堆的内存;例如可执行文件和本机库、本机堆、metaspace、堆外内存分配、堆栈帧、映射文件等。根据您的应用程序,这可能很容易超过 300MB。
其次,OOME 不一定是由运行堆溢出space引起的。检查“原因”字符串的内容。
最后,这可能是您的应用程序在容器化环境中的内存使用率与您 运行 在本地使用时的差异。
有一个 Java 11 (SpringBoot 2.5.1) 应用程序,工作流程简单:
- 上传档案(作为每个大小为 50-100 Mb 的多部分文件)
- 在内存中解压它们
- 通过 JMS 将每个解压缩的文件作为消息发送到队列
当我 运行 在本地 java -jar app.jar
应用程序时,它的内存使用情况(在 VisualVM 中)看起来像锯子:稳定基线(~ 100 Mb)上的峰值(~ 400 Mb)。
当我 运行 同一个应用程序在 Docker 容器中时,内存消耗增长到 700 Mb 甚至更高,直到出现 OutOfMemoryError。 GC 似乎根本不起作用。即使内存选项存在 (java -Xms400m -Xmx400m -jar app.jar
),容器似乎完全忽略它们仍然消耗更多内存。
因此容器中的行为与 OS 中的行为截然不同。
我在 DockerDesktop Windows 10
和 OpenShift 4.6
中尝试了这张 Docker 图片,得到了两张相似的内存使用图片。
Docker文件
FROM bellsoft/liberica-openjdk-alpine:11.0.9-12
RUN addgroup -S apprunner && adduser -S apprunner -G apprunner
COPY target/app.jar /home/apprunner/app.jar
USER apprunner:apprunner
WORKDIR /home/apprunner
EXPOSE 8080
ENTRYPOINT java -Xms400m -Xmx400m -jar app.jar
Java 版本
# HOST
java -version
java 11.0.10 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)
# CONTAINER
java -version
openjdk version "11.0.9" 2020-10-20 LTS
OpenJDK Runtime Environment (build 11.0.9+12-LTS)
OpenJDK 64-Bit Server VM (build 11.0.9+12-LTS, mixed mode)
会不会是 liberica-openjdk v11.0.9-12
中有关闭 GC 之类的“特殊设置”?
请帮我弄清楚这种情况出了什么问题,以及如何使应用程序在容器内外的行为方式相同?
更新: JVM 按预期工作,永远不会超过内存限制。实际上问题在于容器行为:
- 它产生了 200Mb 的开销;
- 它从不释放内存。
当(-Xmx=500m 且容器内存限制为 600Mb)时,#(1) 导致应用程序在 OpenShift 集群中出现 OutOfMemory 错误。所以,事实上,只有 400Mb=(600-200) 可用于 JVM。
#(2) 给人的印象是 GC 不起作用,尽管它确实起作用了:已用内存大小的定期日志记录表明,在增长到峰值(~400Mb)后,它的水平 returns 回到(到~100Mb),同时整体容器内存保持在最高水平(最小减少~20Mb)。
所以这种情况下的解决方案是考虑容器开销并将 OpenShift 容器允许的内存限制设置为 800Mb。
在Java11中,您可以通过在JVM选项中添加-XX:+PrintCommandLineFlags
来找出已传递给JVM的标志以及JVM设置的“符合人体工程学”的标志.
这应该会告诉您您正在使用的容器是否覆盖了您提供的标志。
话虽如此,(IMO) 容器不太可能覆盖参数。
JVM 使用比 -Xmx
选项更多的内存并不罕见。解释是该选项仅控制 Java 堆的大小。 JVM 消耗大量不属于 Java 堆的内存;例如可执行文件和本机库、本机堆、metaspace、堆外内存分配、堆栈帧、映射文件等。根据您的应用程序,这可能很容易超过 300MB。
其次,OOME 不一定是由运行堆溢出space引起的。检查“原因”字符串的内容。
最后,这可能是您的应用程序在容器化环境中的内存使用率与您 运行 在本地使用时的差异。