Kubernetes为运行JVM的Pod投掷OOM

时间:2018-10-01 17:54:21

标签: docker java-8 kubernetes jvm

我正在运行包含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,吊舱就会被杀死并重新创建并启动,因此吊舱中的所有物品都会丢失

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

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

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

3 个答案:

答案 0 :(得分:6)

当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窗格限制设置为两倍的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:

答案 1 :(得分:1)

感谢@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 Is -XX:MaxRAMFraction=1 safe for production in a containered environment?

以下方法会更好,请注意,由于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是有意义的。这也取决于完成的应用程序分析。

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

答案 2 :(得分:0)

如果您能够在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%的可用容器内存,这是旧选项无法实现的。