了解 Docker 中的 Java 内存行为

了解 Docker 中的 Java 内存行为

我们当前在 Google Container Engine (GKE) 中运行的 Kubernetes 集群由n1-standard-1运行 Debian GNU/Linux 7.9 (wheezy) 的机器类型(1 个虚拟 CPU 和 3.75 GB 内存)组成,该版本由 Google 维护。由于我们服务的负载和内存使用量增加,我们需要将节点升级到更大的机器类型。在我们的测试集群中尝试此操作时,我们遇到了一些(对我们来说)似乎相当奇怪的事情。

JVM 应用程序部署到 Google 节点时所消耗的内存似乎与节点上可用的核心数成正比。即使我们将 JVM 最大内存 (Xmx) 设置为 128Mb,在 1 核机器上也会消耗大约 250Mb(这是可以理解的,因为 JVM 消耗的内存比最大限制要多,这是由于 GC、JVM 本身等原因),但在 2 核机器上消耗大约 700Mb(n1-standard-2),在 4 核机器上消耗大约 1.4Gb(n1-standard-4)。唯一不同的是机器类型,使用的 Docker 映像和配置完全相同。

例如,如果我使用某种n1-standard-4机器类型通过 SSH 进入一台机器并运行,sudo docker stats <container_name>我会得到这样的结果:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O             BLOCK I/O
k8s.name  3.84%               1.11 GB / 1.611 GB   68.91%              0 B / 0 B 

当我运行相同的具有精确的 Docker 映像相同的(应用程序)本地配置(mac osx 和 docker-machine)我看到:

CONTAINER CPU %               MEM USAGE / LIMIT    MEM %               NET I/O               BLOCK I/O
name      1.49%               236.6 MB / 1.044 GB  22.66%              25.86 kB / 14.84 kB   0 B / 0 B         

这与我根据设置所期望的结果更加一致Xmx(记录显示我有 8 个核心和 16GB 内存)。当我top -p <pid>在 GKE 实例上运行时,也证实了同样的情况,它为我提供了 1.1 到 1.4 Gb 的 RES/RSS 内存分配。

Docker 镜像定义如下:

FROM java:8u91-jre
EXPOSE 8080
EXPOSE 8081

ENV JAVA_TOOL_OPTIONS -Dfile.encoding=UTF-8

# Add jar
ADD uberjar.jar /data/uberjar.jar

CMD java -jar /data/uberjar.jar -Xmx128m -server

我也尝试过添加:

ENV MALLOC_ARENA_MAX 4

我已经在多个帖子中看到推荐,例如但似乎没有任何区别。我也尝试过更改为另一个 Java 基础映像以及使用 alpine linux,但这似乎也没有什么改变。

我本地的 Docker 版本是 1.11.1,Kubernetes/GKE 中的 Docker 版本是 1.9.1。Kubernetes 版本(如果有关系)是 v1.2.4。

同样有趣的是,如果我们将 pod/容器的多个实例部署到同一台机器/节点,某些实例将分配更少的内存。例如,前三个可能分配 1.1-1.4Gb 内存,但随后的 10 个容器每个仅分配约 250 Mb,这大致符合我的预期每一个实例分配。问题是,如果我们达到机器的内存限制,前三个实例(分配 1.1Gb 内存的实例)似乎永远不会释放它们分配的内存。如果它们在机器压力增加时释放内存,我不会担心这个问题,但由于它们在机器加载时仍保留内存分配,因此这成为一个问题(因为它禁止在此机器上安排其他容器,因此浪费了资源)。

问题:

  1. 什么原因导致了这种行为?是 JVM 问题吗?Docker 问题?VM 问题?Linux 问题?配置问题?或者是两者兼而有之?
  2. 在这种情况下我可以尝试做些什么来限制 JVM 的内存分配?

答案1

当您指定

CMD java -jar /data/uberjar.jar -Xmx128m -server

然后,您想要作为 JVM 参数的值 ( -Xmx128m -server) 将作为命令行参数传递给 .jar 文件中的 Main 类。它们可用作方法中的参数public static void main(String... args)

请注意,如果您按名称运行主类而不是指定可执行文件,也是如此jar

要将它们作为 JVM 参数传递,而不是将其作为程序的参数传递,您需要在参数之前指定它们-jar

https://stackoverflow.com/questions/5536476/passing-arguments-to-jar-which-is-required-by-java-interpreter

答案2

问题出在 中指定的 JVM 设置上Dockerfile。我们像这样启动了 JVM:

CMD java -jar /data/uberjar.jar -Xmx128m -server

但当我们切换到:

CMD java -Xmx128m -server -jar /data/uberjar.jar

设置已被考虑在内。我仍然不明白为什么我没有在本地看到这个问题,但我很高兴我们设法解决了这个问题。

相关内容