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

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

时间:2022-11-29 22:44:35浏览次数:65  
标签:容器 Kubernetes VM XX 内存 JVM docker

背景介绍

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

参考资料

标签:容器,Kubernetes,VM,XX,内存,JVM,docker
From: https://www.cnblogs.com/liboware/p/16936982.html

相关文章

  • 内存分页机制
    引言:操作系统和硬件是相互依赖和发展起来的。程序需要分段,操作系统提供分页。在保护模式下,段描述符GDT是内存段的身份证。GDTR寄存器保存了GDT的起始地址。通过选择子去......
  • 内存图
    ......
  • 0 工作方式和内存管理
    0工作方式及内存管理0.0工作方式什么是Java程序?如何进行提交?Java程序是由一组类所组成的,其中有一个类会带有启动用的main()方法。因此程序员必须要编写一或多个类并......
  • 操作系统 内存空间的扩充(虚拟内存)
    覆盖与交换 1.覆盖与交换覆盖,交换,虚拟存储技术常用于实现内存空间的扩充 1.1覆盖技术覆盖技术的思想:将程序分为多个段,常用的段常驻内存,不常用的段在需要的时......
  • JMM(java内存模型)
                                println方法中加了synchronized关键字,保证了可见性与原子性。     ......
  • 内存磁盘告警练习
    #!/usr/bin/bashmem_used=`free-m|grep'^Mem:'|awk'{print$3}'`mem_total=`free-m|grep'^Mem:'|awk'{print$2}'`#free-m以MB显示mem_percent=$((m......
  • Kubernetes 控制平面组件:API Server
    一、APIServer的概念kube-apiserver是Kubernetes最重要的核心组件之一,主要提供以下的功能:  提供集群管理的RESTAPI接口,包括认证授权、数据校验以及集群......
  • Android - DataBinding源码解读(内存消耗和双向绑定原理分析)
    目录​​一代码Demo​​​​二解析​​​​2.1 关键的ActivityMainBindingImp()​​​​2.2 ​​​​2.3​​​​三总结​​​​3.1内存消耗的三个地方:​​​​3.2 ......
  • Android:性能优化工具之内存泄露-LeakCanary
    目录​​一简介​​​​二使用​​​​三进阶用法​​​​四hprof分析复杂内存泄露问题​​​​五使用小结​​​​六使用踩坑​​​​6.1权限​​​​6.2 NullPoi......
  • Kubernetes 多集群管理平台 OCM v0.9.0 发布:进一步改善托管集群安全性问题
    作者:BraeTroutman随着OpenClusterManagement(OCM)项目的持续发展,我们觉得有必要周期性向大家同步近期项目的一些进展了,包括我们我们下一步未来发展的方向以及作为贡献者如何......