【问题标题】:Kubernetes throwing OOM for pods running a JVMKubernetes 为运行 JVM 的 pod 抛出 OOM
【发布时间】:2019-03-06 20:59:21
【问题描述】:

我正在运行包含 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。

【问题讨论】:

  • 你设置了-XX:+UseCGroupMemoryLimitForHeap -Xms -XX:MaxRAM=1536M。你明白他们的意思吗?他们是怎么做到的interact
  • 关于你的-XX:+HeapDumpOnOutOfMemoryError问题,在你到达堆转储之前容器重启 - 如果JVM实际上已经OOM,那么你需要在主机节点上安装一个卷并将转储写入该路径。看这里kubernetes.io/docs/tasks/configure-pod-container/…
  • 感谢 @Eugene 指出 JVM 参数。现在我明白 Xmx 会覆盖 Cgrouplimits。我还在本地进行了一些测试,发现提供 MaxRamFraction 和 MaxRAM 是必不可少的,例如docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 -XX:MaxRAM=700M -XX:MaxMetaspaceSize=200M -XshowSettings:vm -version VM 设置:最大。堆大小(估计):622.50M 人体工程学机器类:服务器使用 VM:OpenJDK 64 位服务器 VM。现在,我需要再做一些测试来指定 -XX:MaxMetaspaceSize
  • 感谢 @nickebbitt 在转储到已安装卷时指出 k8 链接。

标签: docker java-8 kubernetes jvm


【解决方案1】:

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

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

Java 应用程序消耗的内存不限于 Heap 的大小,您可以通过指定选项来设置:

-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)的数量计算堆大小。 article 中有一个很好的解释它是如何工作的:

默认大小基于机器上的内存量,可以使用-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 的好方法:

【讨论】:

    【解决方案2】:

    如果您能够在 Java 11(或 10)而不是 8 上运行,memory limit options 将得到很大改进(另外 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% 的可用容器内存,这在旧选项中是不可能的。

    【讨论】:

    • MaxRAMPercentage 也从 u131 开始向后移植到 JDK8。
    【解决方案3】:

    感谢@VAS 您的 cmets。感谢您的 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 人体工学机器类:服务器 使用虚拟机:OpenJDK 64 位服务器虚拟机

    现在指定 XX:MaxRAMFraction=1 可能会被杀死:

    参考:https://twitter.com/csanchez/status/940228501222936576?lang=en Is -XX:MaxRAMFraction=1 safe for production in a containered environment?

    以下会更好,请注意我已经删除了 MaxRAM,因为 MaxRAMFraction > 1 :

    $ docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XshowSettings:vm -version 虚拟机设置: 最大限度。堆大小(估计):455.50M 人体工学机器类:服务器 使用虚拟机:OpenJDK 64 位服务器虚拟机

    这为本地提供了 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 人体工学机器类:服务器 使用虚拟机:OpenJDK 64 位服务器虚拟机

    从逻辑上讲,也根据上述参考,指定 -XX:MaxRAMFraction >1 是有意义的。这也取决于完成的应用程序分析。

    我还在做更多的测试,会更新这些结果或发布。谢谢

    【讨论】:

      【解决方案4】:

      最近我也遇到了类似的问题

      java 11.0.11+9 + kubernetes 在 pod 中运行 docker 容器

      与 op 类似的配置

      resources:
          requests:
              memory: "1G"
              cpu: 400m
          limits:
              memory: "1G"
      

      -XX:MaxRAMPercentage=60.0

      我们的服务会上传和下载大量数据。因此使用直接内存和in this 问题我发现MaxDirectMemorySize 等于堆大小。因此,如果我们计算内存使用量,它可能会落后于限制 1G (1G * 0.6 * 2)。在这种情况下,我们将内存增加到1.5G 并更改了-XX:MaxRAMPercentage=35.0,因此我们有足够的空间用于堆+直接内存,甚至用于一些与操作系统相关的任务。在容器环境中设置MaxRAMPercentageXmx时要小心。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-17
        • 1970-01-01
        • 1970-01-01
        • 2021-09-01
        • 1970-01-01
        • 2019-05-14
        • 1970-01-01
        相关资源
        最近更新 更多