首页 > 解决方案 > Kubernetes 为运行 JVM 的 pod 抛出 OOM

问题描述

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

  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 中的所有内容都会丢失

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

  4. 我还使用 visualVM、jHat 对代码进行了分析,但找不到大量内存占用,这可能导致 JVM 中运行的线程消耗过多内存或可能存在泄漏的结论。

感谢任何帮助解决 Kubernetes 引发的 OOM。

标签: dockerjava-8kubernetesjvm

解决方案


当 Pod 中的应用程序达到您通过 resources.limits.memory 或命名空间限制设置的内存限制时,Kubernetes 会重新启动 Pod。

Kubernetes限制资源的部分在以下文章中有所描述:

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 内存使用的第二种方法是根据 RAM(或 MaxRAM)的数量计算堆大小。在文章中有一个很好的解释它是如何工作的:

默认大小基于机器上的内存量,可以使用 -XX:MaxRAM=N 标志设置。通常,该值由 JVM 通过检查机器上的内存量来计算。但是,JVM 限制 MaxRAM1 GB客户端编译器、4 GB32 位服务器编译器和128 GB64 位编译器。最大堆大小是 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 的好方法:


推荐阅读