写点什么

【JVM 故障问题排查心得】「内存诊断系列」JVM 内存与 Kubernetes 中 pod 的内存、容器的内存不一致所引发的 OOMKilled 问题总结(上)

作者:洛神灬殇
  • 2022-12-01
    江苏
  • 本文字数:2468 字

    阅读完需:约 8 分钟

【JVM故障问题排查心得】「内存诊断系列」JVM内存与Kubernetes中pod的内存、容器的内存不一致所引发的OOMKilled问题总结(上)

背景介绍

在我们日常的工作当中,通常应用都会采用 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 -versionVM 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 -versionVM 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 -versionVM 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 -versionVM 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 选项来压缩容器中的所有内存。

参考资料

  • https://blog.csdn.net/maoreyou/article/details/80134303

  • https://blogs.oracle.com/java/post/java-se-support-for-docker-cpu-and-memory-limits

  • https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8186315

  • https://www.javacodegeeks.com/2016/02/simplicity-value-hotspots-xshowsettings-flag.html

发布于: 刚刚阅读数: 3
用户头像

洛神灬殇

关注

🏆InfoQ写作平台-签约作者🏆 2020-03-25 加入

【个人简介】酷爱计算机科学、醉心编程技术、喜爱健身运动、热衷悬疑推理的“极客达人” 【技术格言】任何足够先进的技术都与魔法无异 【技术范畴】Java领域、Spring生态、MySQL专项、微服务/分布式体系和算法设计等

评论

发布
暂无评论
【JVM故障问题排查心得】「内存诊断系列」JVM内存与Kubernetes中pod的内存、容器的内存不一致所引发的OOMKilled问题总结(上)_Docker_洛神灬殇_InfoQ写作社区