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

简介: 【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 -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选项来压缩容器中的所有内存。




参考资料





相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
12月前
|
缓存 监控 Java
说一说 SpringCloud Gateway 堆外内存溢出排查
我是小假 期待与你的下一次相遇 ~
1445 5
|
Prometheus Kubernetes 监控
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
深入探索Kubernetes中的Pod自动扩展(Horizontal Pod Autoscaler, HPA)
|
Kubernetes Docker 容器
Kubernetes与Docker参数对照:理解Pod中的command、args与Dockerfile中的CMD、ENTRYPOINT。
需要明确的是,理解这些都需要对Docker和Kubernetes有一定深度的理解,才能把握二者的区别和联系。虽然它们都是容器技术的二个重要组成部分,但各有其特性和适用场景,理解它们的本质和工作方式,才能更好的使用这些工具,将各自的优点整合到生产环境中,实现软件的快速开发和部署。
574 25
|
Kubernetes Shell Windows
【Azure K8S | AKS】在AKS的节点中抓取目标POD的网络包方法分享
在AKS中遇到复杂网络问题时,可通过以下步骤进入特定POD抓取网络包进行分析:1. 使用`kubectl get pods`确认Pod所在Node;2. 通过`kubectl node-shell`登录Node;3. 使用`crictl ps`找到Pod的Container ID;4. 获取PID并使用`nsenter`进入Pod的网络空间;5. 在`/var/tmp`目录下使用`tcpdump`抓包。完成后按Ctrl+C停止抓包。
541 12
|
存储 Kubernetes Docker
【赵渝强老师】Kubernetes中Pod的基础容器
Pod 是 Kubernetes 中的基本单位,代表集群上运行的一个进程。它由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。基础容器负责维护 Pod 的网络空间,对用户透明。文中附有图片和视频讲解,详细介绍了 Pod 的组成结构及其在网络配置中的作用。
318 1
【赵渝强老师】Kubernetes中Pod的基础容器
|
运维 Kubernetes Shell
【赵渝强老师】K8s中Pod的临时容器
Pod 是 Kubernetes 中的基本调度单位,由一个或多个容器组成,包括业务容器、基础容器、初始化容器和临时容器。临时容器用于故障排查和性能诊断,不适用于构建应用程序。当 Pod 中的容器异常退出或容器镜像不包含调试工具时,临时容器非常有用。文中通过示例展示了如何使用 `kubectl debug` 命令创建临时容器进行调试。
376 1
|
11月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3237 0
|
11月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1060 1

相关产品

  • 容器服务Kubernetes版
  • 推荐镜像

    更多