本文正在参与「金石计划 . 瓜分6万现金大奖」

布景介绍

在咱们日常的作业当中,通常应用都会选用Kubernetes进行容器化布置,可是总是会呈现一些问题,例如,JVM堆小于Docker容器中设置的内存巨细和Kubernetes的内存巨细,可是仍是会被OOMKilled。在此咱们介绍一下K8s的OOMKilled的Exit Code编码。

Exit Code 137

  • 标明容器收到了 SIGKILL 信号,进程被杀掉,对应kill -9,引发SIGKILL的是docker kill。这能够由用户或由docker看护程序来发起,手动履行:docker kill
  • 137比较常见,假如 pod 中的limit 资源设置较小,会运转内存不足导致 OOMKilled,此时state 中的 ”OOMKilled” 值为true,你能够在体系的dmesg -T 中看到OOM日志。

为什么我设置的巨细联系没有错,还会OOMKilled?

由于我的heap巨细肯定是小于Docker容器以及Pod的巨细的,为啥仍是会呈现OOMKilled?

原因剖析

这种问题常发生在JDK8u131或许JDK9版别之后所呈现在容器中运转JVM的问题:在大多数情况下,JVM将一般默许会选用宿主机Node节点的内存为Native VM空间(其中包含了堆空间、直接内存空间以及栈空间),而并非是是容器的空间为标准。

例如在我的机器

docker run -m 100MB openjdk:8u121 java -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 444.50M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

以上的信息呈现了矛盾,咱们在运转的时分将容器内存设置为100MB,而-XshowSettings:vm打印出的JVM将最大堆巨细为444M,假如按照这个内存进行分配内存的话很可能会导致节点主机在某个时分杀死我的JVM。

如何处理此问题

JVM 感知 cgroup 限制

一种办法处理 JVM 内存超限的问题,这种办法能够让JVM自动感知 docker 容器的 cgroup 限制,然后动态的调整堆内存巨细。JDK8u131在JDK9中有一个很好的特性,即JVM能够检测在Docker容器中运转时有多少内存可用。为了使jvm保存根据容器规范的内存,必须设置标志-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap。

注意:假如将这两个标志与Xms和Xmx标志一起设置,那么jvm的行为将是什么?-Xmx标志将覆盖-XX:+ UseCGroupMemoryLimitForHeap标志。

总结一下
  • 标志-XX:+ UseCGroupMemoryLimitForHeap使JVM能够检测容器中的最大堆巨细。

  • -Xmx标志将最大堆巨细设置为固定巨细。

  • 除了JVM的堆空间,还会关于非堆和jvm的东西,还会有一些额定的内存运用情况。

运用JDK9的容器感知机制测验

$ docker run -m 100MB openjdk:8u131 java \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseCGroupMemoryLimitForHeap \
  -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 44.50M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

能够看出来经过内存感知之后,JVM能够检测到容器只有100MB,并将最大堆设置为44M。咱们调整一下内存巨细看看是否能够完成动态化调整和感知内存分配,如下所示。

docker run -m 1GB openjdk:8u131 java \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseCGroupMemoryLimitForHeap \
  -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 228.00M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

咱们设置了容器有1GB内存分配,而JVM运用228M作为最大堆。由于容器中除了JVM之外没有其他进程在运转,所以咱们还能够进一步扩展一下关于Heap堆的分配?

$ docker run -m 1GB openjdk:8u131 java \
  -XX:+UnlockExperimentalVMOptions \
  -XX:+UseCGroupMemoryLimitForHeap \
  -XX:MaxRAMFraction=1 -XshowSettings:vm -version
VM settings:
    Max. Heap Size (Estimated): 910.50M
    Ergonomics Machine Class: server
    Using VM: OpenJDK 64-Bit Server VM

在较低的版别的时分能够运用-XX:MaxRAMFraction参数,它告诉JVM运用可用内存/MaxRAMFract作为最大堆。运用-XX:MaxRAMFraction=1,咱们将简直一切可用内存用作最大堆。从上面的成果能够看出来内存分配已经能够达到了910.50M。

问题剖析
  1. 最大堆占用总内存是否依然会导致你的进程由于内存的其他部分(如“元空间”)而被杀死?
  • 答案:MaxRAMFraction=1仍将为其他非堆内存留出一些空间。

但假如容器运用堆外内存,这可能会有风险,由于简直一切的容器内存都分配给了堆。您必须将-XX:MaxRAMFraction=2设置为堆只运用50%的容器内存,或许运用Xmx

容器内部感知CGroup资源限制

Docker1.7开端将容器cgroup信息挂载到容器中,所以应用能够从 /sys/fs/cgroup/memory/memory.limit_in_bytes 等文件获取内存、 CPU等设置,在容器的应用启动命令中根据Cgroup装备正确的资源设置 -Xmx, -XX:ParallelGCThreads等参数

在Java10中,改进了容器集成。
  • Java10+废除了-XX:MaxRAM参数,由于JVM将正确检测该值。在Java10中,改进了容器集成。无需添加额定的标志,JVM将运用1/4的容器内存用于堆。

  • java10+确实正确地识别了内存的docker限制,但您能够运用新的标志MaxRAMPercentage(例如:-XX:MaxRAMPercentage=75)而不是旧的MaxRAMFraction,以便更精确地调整堆的巨细,而不是其余的(堆栈、本机…)

  • java10+上的UseContainerSupport选项,并且是默许启用的,不必设置。一起 UseCGroupMemoryLimitForHeap 这个就弃用了,不建议持续运用,一起还能够经过 -XX:InitialRAMPercentage、-XX:MaxRAMPercentage、-XX:MinRAMPercentage 这些参数更加细腻的控制 JVM 运用的内存比率。

Java 程序在运转时会调用外部进程、请求 Native Memory 等,所以即使是在容器中运转 Java 程序,也得预留一些内存给体系的。所以 -XX:MaxRAMPercentage 不能装备得太大。当然依然能够运用-XX:MaxRAMFraction=1选项来压缩容器中的一切内存。

参考资料

  • blog.csdn.net/maoreyou/ar…
  • blogs.oracle.com/java/post/j…
  • bugs.java.com/bugdatabase…
  • www.javacodegeeks.com/2016/02/sim…