Kubernetes 为运行 JVM 的 pod 抛出 OOM



我正在运行包含JVM(java8u31)的Docker容器。这些容器作为 Pod 部署在 Kubernetes 集群中。通常我会为 pod 获取 OOM,而 Kubernetes 会杀死 pod 并重新启动它。我在寻找这些 OOM 的根本原因时遇到了问题,因为我是 Kubernetes 的新手。

  1. 以下是 JVM 参数

    -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Xms700M -Xmx1000M  -XX:MaxRAM=1536M  -XX:MaxMetaspaceSize=250M 
    
  2. 这些容器部署为有状态集,以下是资源分配

    resources:
    requests:
    memory: "1.5G"
    cpu: 1
    limits:
    memory: "1.5G"
    cpu: 1
    

    因此分配给容器的总内存与 MaxRam 匹配

  3. 如果我使用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/etc/opt/jmx/java_pid%p.hprof那没有帮助,因为一旦有 OOM,pod 就会被杀死并重新创建并启动,因此 pod 中的所有内容都会丢失

    获取线程或堆转储的唯一方法是通过 SSH 连接到 pod,我也无法接受,因为 pod 是在 OOM 之后重新创建的,所以我在 OOM 时不会获得内存占用。我在 OOM 之后 SSH 没有太大帮助。

  4. 我还使用 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 GB128 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因此我们有足够的空间用于堆 + 直接内存,甚至用于一些与操作系统相关的任务。在容器环境中设置MaxRAMPercentageXmx时要小心。

最新更新