Kubernetes为运行JVM的Pod抛出OOM

use*_*882 3 jvm java-8 docker kubernetes

我正在运行包含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 
    
    Run Code Online (Sandbox Code Playgroud)
  2. 这些容器被部署为有状态集,以下是资源分配

    resources:
        requests:
            memory: "1.5G"
            cpu: 1
        limits:
            memory: "1.5G"
            cpu: 1
    
    Run Code Online (Sandbox Code Playgroud)

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

  3. 如果我使用-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/etc/opt/jmx/java_pid%p.hprof该方法无济于事,因为一旦有OOM,吊舱就会被杀死,重新创建并启动,因此吊舱中的所有物品都会丢失

    获取线程或HEAP转储的唯一方法是SSH到Pod中,这也是我无法接受的,因为Pod是在OOM之后重新创建的,所以在OOM时我没有占用内存。我在OOM之后进行SSH,这没有太大帮助。

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

感谢您提供任何帮助来解决Kubernetes抛出的OOM。

VAS*_*VAS 7

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

以下文章介绍了限制资源的Kubernetes部分:

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

-Xmssize Specifies the initial heap size.
-Xmxsize Specifies the maximum heap size.
Run Code Online (Sandbox Code Playgroud)

Java应用程序需要一些额外的内存来存储元空间,类空间,堆栈大小,而JVM本身甚至需要更多内存来执行其任务,例如垃圾收集,JIT优化,堆外分配,JNI代码。很难以合理的精度预测JVM的总内存使用情况,因此最好的方法是在具有常规负载的实际部署中对其进行度量。

我建议您将Kubernetes窗格限制设置为两倍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>
Run Code Online (Sandbox Code Playgroud)

可以在以下文章中找到关于此的更多详细信息:

限制JVM内存使用的第二种方法是根据RAM(或MaxRAM)的数量来计算堆大小。在文章中有一个很好的解释:

默认大小基于计算机上的内存量,可以使用该-XX:MaxRAM=N 标志设置 。通常,该值由JVM通过检查计算机上的内存量来计算。然而,JVM限制 MaxRAM1 GB为客户机的编译器,4 GB用于32位服务器的编译器,以及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:


Kol*_*l00 5

如果您能够在 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)
Run Code Online (Sandbox Code Playgroud)

这样,您可以轻松地为堆指定 80% 的可用容器内存,这在旧选项中是不可能的。


use*_*882 2

感谢@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 是有意义的。这还取决于完成的应用程序分析。

我仍在做更多测试,将更新这些结果或发布。谢谢