我正在运行包含JVM(java8u31)的Docker容器。这些容器作为 Pod 部署在 Kubernetes 集群中。通常我会为 pod 获取 OOM,而 Kubernetes 会杀死 pod 并重新启动它。我在寻找这些 OOM 的根本原因时遇到了问题,因为我是 Kubernetes 的新手。
-
以下是 JVM 参数
-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms700M -Xmx1000M -XX:MaxRAM=1536M -XX:MaxMetaspaceSize=250M
-
这些容器部署为有状态集,以下是资源分配
resources: requests: memory: "1.5G" cpu: 1 limits: memory: "1.5G" cpu: 1
因此分配给容器的总内存与 MaxRam 匹配
-
如果我使用
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/etc/opt/jmx/java_pid%p.hprof
那没有帮助,因为一旦有 OOM,pod 就会被杀死并重新创建并启动,因此 pod 中的所有内容都会丢失获取线程或堆转储的唯一方法是通过 SSH 连接到 pod,我也无法接受,因为 pod 是在 OOM 之后重新创建的,所以我在 OOM 时不会获得内存占用。我在 OOM 之后 SSH 没有太大帮助。
-
我还使用 visualVM、jHat 分析了代码,但找不到大量的内存占用,这可能导致 JVM 中运行的线程消耗过多内存或可能泄漏的结论。
感谢任何帮助来解决 Kubernetes 抛出的 OOM。
当 Pod 中的应用程序达到 resources.limits.memory 或命名空间限制设置的内存限制时,Kubernetes 会重新启动 Pod。
限制资源的 Kubernetes 部分在以下文章中进行了描述:
- Kubernetes 最佳实践:资源请求和限制
- 资源配额
- 准入控制插件:资源配额
- 将内存资源分配给容器和 Pod。
Java 应用程序使用的内存不限于可以通过指定选项来设置的堆的大小:
-Xmssize Specifies the initial heap size.
-Xmxsize Specifies the maximum heap size.
Java 应用程序需要一些额外的内存用于元空间、类空间、堆栈大小,而 JVM 本身需要更多的内存来完成其任务,如垃圾回收、JIT 优化、堆外分配、JNI 代码。 很难以合理的精度预测 JVM 的总内存使用量,因此最好的方法是在具有通常负载的实际部署中测量它。
我建议你将 Kubernetes pod 限制设置为Xmx
大小的两倍,检查你是否不再获得 OOM,然后逐渐将其降低到你开始获得 OOM 的程度。最终值应该在这些点之间.
您可以从像 Prometheus 这样的监控系统中的内存使用情况统计信息中获得更精确的值。
另一方面,您可以尝试通过指定可用选项的数量来限制 java 内存使用量,如下所示:
-Xms<heap size>[g|m|k] -Xmx<heap size>[g|m|k]
-XX:MaxMetaspaceSize=<metaspace size>[g|m|k]
-Xmn<young size>[g|m|k]
-XX:SurvivorRatio=<ratio>
有关这方面的更多详细信息,请参阅以下文章:
- 正确限制 JVM 的内存使用量(Xmx 还不够)
- 为什么我的 Java 进程消耗的内存比 Xmx 多
限制 JVM 内存使用的第二种方法是根据 RAM(或 MaxRAM)的数量计算堆大小。文章中对它的工作原理有一个很好的解释:
默认大小基于计算机上的内存量,可以使用
-XX:MaxRAM=N
标志进行设置。 通常,JVM 通过检查机器上的内存量来计算该值。 但是,JVM 将MaxRAM
限制为客户端编译器的1 GB
、32 位服务器编译器的4 GB
和 64 位编译器的128 GB
。 最大堆大小为MaxRAM
的四分之一。 这就是默认堆大小可以变化的原因:如果计算机上的物理内存小于MaxRAM
,则默认堆大小是该大小的四分之一。 但是即使有数百GB的RAM可用,默认情况下JVM将使用的最多是32 GB
:128 GB
的四分之一。默认的最大堆计算实际上是这样的:
Default Xmx = MaxRAM / MaxRAMFraction
因此,也可以通过调整 -
XX:MaxRAMFraction=N
标志的值来设置默认的最大堆,该标志默认为4
。 最后,为了让事情变得有趣,-XX:ErgoHeapSizeLimit=N
标志也可以设置为 JVM 应该使用的最大默认值。 默认情况下0
该值(意味着忽略它);否则,如果该限制小于MaxRAM / MaxRAMFraction
,则使用该限制。
初始堆大小选择类似,但复杂性较低。初始堆大小值按如下方式确定:
Default Xms = MaxRAM / InitialRAMFraction
从默认的最小堆大小可以得出结论,
InitialRAMFraction
标志的默认值为64
。 如果该值小于5 MB
— 或者严格来说,小于-XX:OldSize=N
(默认为4 MB
) 加上 -XX:NewSize=N
(默认为1 MB
) 指定的值,则会出现此处的一个警告。 在这种情况下,旧大小和新大小的总和将用作初始堆大小。
本文为您提供了一个开始为面向 Web 的应用程序调整 JVM 的好点:
- 应在生产中始终使用的 Java VM 选项
如果你能够在Java 11(或10)而不是8上运行,那么内存限制选项已经有了很大的改进(加上JVM是cgroups感知的)。只需使用-XX:MaxRAMPercentage
(范围 0.0、100.0):
$ docker run -m 1GB openjdk:11 java -XshowSettings:vm -XX:MaxRAMPercentage=80 -version
VM settings:
Max. Heap Size (Estimated): 792.69M
Using VM: OpenJDK 64-Bit Server VM
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment (build 11.0.1+13-Debian-2)
OpenJDK 64-Bit Server VM (build 11.0.1+13-Debian-2, mixed mode, sharing)
这样,您可以轻松地为堆指定 80% 的可用容器内存,这在旧选项中是不可能的。
感谢您的评论@VAS。感谢您的 kubernetes 链接。
经过几次测试,我认为如果您使用的是 -XX:+UseCGroupMemoryLimitForHeap,则指定 XMX 不是一个好主意,因为 XMX 会覆盖它。我仍在做更多的测试和剖析。
因为我的要求是在 docker 容器内运行 JVM。正如@Eugene帖子中提到的,我做了一些测试。考虑到在JVM中运行的每个应用程序都需要HEAP和一些本机内存,我认为我们需要指定-XX:+UnlockExperimentalVMOptions,XX:+UseCGroupMemoryLimitForHeap,-XX:MaxRAMFraction=1(只考虑在容器内运行的JVM,同时它有风险)-XX:MaxRAM(我认为如果MaxRAMFraction为1,我们应该指定这一点,以便为本机内存留下一些)
几个测试:
根据下面的 docker 配置,考虑到您只有 JVM 在容器内运行,docker 被分配了 1 GB。考虑到 docker 分配给 1G,我也想为进程/本机内存分配一些,我认为我应该使用 MaxRam=700M,以便我有 300 MB 的本机内存。
$ docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XX:MaxRAM=700M -XshowSettings:vm -version 虚拟机设置: 最大堆大小(估计):622.50M 人体工程学 机器类别:服务器 使用 VM:OpenJDK 64 位服务器 VM
现在指定 XX:MaxRAMFraction=1 可能会造成伤害:
参考资料: https://twitter.com/csanchez/status/940228501222936576?lang=en -XX:MaxRAMFraction=1 在容器环境中生产是否安全?
以下会更好,请注意,自从MaxRAMFraction>1以来,我已经删除了MaxRAM
:$ docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XshowSettings:vm -version 虚拟机设置: 最大堆大小(估计):455.50M 人体工程学 机器类别:服务器 使用 VM:OpenJDK 64 位服务器 VM
这给出了本机 500M 的其余部分,例如可以通过指定 -XX:MaxMetaspaceSize 用于 MetaSpace:
$ docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XX:MaxMetaspaceSize=200M -XshowSettings:vm -version 虚拟机设置: 最大堆大小(估计):455.50M 人体工程学 机器类别:服务器 使用 VM:OpenJDK 64 位服务器 VM
从逻辑上讲,根据上述参考,指定 -XX:MaxRAMFraction>1 是有意义的。这也取决于完成的应用程序分析。
我还在做更多的测试,将更新这些结果或发布。
谢谢最近我也遇到了类似的问题
Java 11.0.11+9 + 在 Pod 中运行 Docker 容器的 Kubernetes
与 OP 类似的配置
resources:
requests:
memory: "1G"
cpu: 400m
limits:
memory: "1G"
与-XX:MaxRAMPercentage=60.0
我们的服务上传和下载大量数据。因此,正在使用直接内存,在本期中,我发现MaxDirectMemorySize
等于堆大小。因此,如果我们计算内存使用情况,它可能会低于限制1G
(1G * 0.6 * 2)。在这种情况下,我们增加了内存以1.5G
并更改了-XX:MaxRAMPercentage=35.0
因此我们有足够的空间用于堆 + 直接内存,甚至用于一些与操作系统相关的任务。在容器环境中设置MaxRAMPercentage
或Xmx
时要小心。