背景介绍

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

参考资料

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

  1. ☕【Java深层系列】「并发编程系列」深入分析和研究MappedByteBuffer的实现原理和开发指南

    前言介绍 在Java编程语言中,操作文件IO的时候,通常采用BufferedReader,BufferedInputStream等带缓冲的IO类处理大文件,不过java nio中引入了一种基于Mapp ...

  2. 【Netty技术专题】「原理分析系列」Netty强大特性之ByteBuf零拷贝技术原理分析

    零拷贝Zero-Copy 我们先来看下它的定义: "Zero-copy" describes computer operations in which the CPU does n ...

  3. 「美团面试系列」面试加分项,这样说你会JVM,面试官还能问什么

    Java性能调优都是老生常谈的问题,特别当“糙快猛”的开发模式大行其道时,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 比如,下面这些典型的性能问题,你肯定或多或少都遇到过: 在进行性 ...

  4. 【Java技术专题】「性能优化系列」针对Java对象压缩及序列化技术的探索之路

    序列化和反序列化 序列化就是指把对象转换为字节码: 对象传递和保存时,保证对象的完整性和可传递性.把对象转换为有字节码,以便在网络上传输或保存在本地文件中: 反序列化就是指把字节码恢复为对象: 根据字 ...

  5. ☕【难点攻克技术系列】「海量数据计算系列」如何使用BitMap在海量数据中对相应的进行去重、查找和排序

    BitMap(位图)的介绍 BitMap从字面的意思,很多人认为是位图,其实准确的来说,翻译成基于位的映射,其中数据库中有一种索引就叫做位图索引. 在具有性能优化的数据结构中,大家使用最多的就是has ...

  6. 「数据挖掘入门系列」Python快速入门

    Python环境搭建 本次入门系列将使用Python作为开发语言.要使用Python语言,我们先来搭建Python开发平台.我们将基于Python 2.7版本.以及Python的开发发行版本Anaco ...

  7. 「React Native笔记」在React的 setState 中操作数组和对象的多种方法(合集)

    运用在React 中 setState的对象.数组的操作时是不能用类似array.push()等方法,因为push没有返回值,setState后会出现state变成Number,为了方便他人和自己查看 ...

  8. 🏆(不要错过!)【CI/CD技术专题】「Jenkins实战系列」(3)Jenkinsfile+DockerFile实现自动部署

    每日一句 没有人会因学问而成为智者.学问或许能由勤奋得来,而机智与智慧却有懒于天赋. 前提概要 Jenkins下用DockerFile自动部署Java项目,项目的部署放心推向容器化时代机制. 本节需要 ...

  9. 【分布式技术专题】「OSS中间件系列」Minio的文件服务的存储模型及整合Java客户端访问的实战指南

    Minio的元数据 数据存储 MinIO对象存储系统没有元数据数据库,所有的操作都是对象级别的粒度的,这种做法的优势是: 个别对象的失效,不会溢出为更大级别的系统失效. 便于实现"强一致性& ...

  10. 🏆【CI/CD技术专题】「Docker实战系列」(1)本地进行生成镜像以及标签Tag推送到DockerHub

    背景介绍 Docker镜像构建成功后,只要有docker环境就可以使用,但必须将镜像推送到Docker Hub上去.创建的镜像最好要符合Docker Hub的tag要求,因为在Docker Hub注册 ...

随机推荐

  1. 连接Vue.js作为前端,Fastapi作为后端

    项目结构 ├── main.py └── templates └── home.html 环境安装 pip install fastapi[all] pip install jinja2 Backen ...

  2. 存储类StorageClass

    存储类概述 StorageClass 存储类用于描述集群中可以提供的存储的类型.不同的存储类可能对应着不同的: 服务等级(quality-of-service level) 备份策略 集群管理员自定义 ...

  3. 7.prometheus监控多个MySQL实例

    mysqld_exporter集中部署 集中部署,就是说我们将所有的mysqld_exporter部署在同一台服务器上,在这台服务器上对mysqld_exporter进行统一的管理,下面介绍一下集中部 ...

  4. Jenkins和Gitlab CI/CD自动更新k8s中pod使用的镜像说明

    Jenkins 使用Jenkins的话,完成的工作主要有如下步骤: 1.从Gogs或Gitlab仓库上拉取代码 2.使用Maven编译代码,打包成jar文件 3.根据jar文件使用相对应的Docker ...

  5. JS 模块化- 05 ES Module & 4 大规范总结

    1 ES Module 规范 ES Module 是目前使用较多的模块化规范,在 Vue.React 中大量使用,大家应该非常熟悉.TypeScript 中的模块化与 ES 类似. 1.1 导出模块 ...

  6. Docker | dockerfile构建centos镜像,以及CMD和ENTRYPOINT的区别

    构建自己的centos镜像 docker pull centos下载下来的镜像都是基础版本,缺少很多常用的命令功能,比如:ll.vim等等, 下面介绍制作一个功能较全的自己的centos镜像. 步骤 ...

  7. Java I/O(1):模型与流

    在1990年以前,有一帮工程师们认为未来(1990年以后)会有很多小型设备需要得到电脑操控(不得不说,想法非常超前),鉴于当时市面上并没有任何一款编程语言能够跨平台,而且能够在诸如烤面包机这种小型设备 ...

  8. for循环小九九乘法表

    for(int i=1;i<=9;i++) { for(int j=1;j<=i;j++) { System.out.print(i+"*"+j+"=&quo ...

  9. Linux基础_1_简介

    Linux是什么 一款优秀的操作系统软件,特性是一切皆文件:一切设备皆文件!一切设备的设置皆修改配置文件!一切服务的搭建皆修改配置文件!(庞大的树形结构文件系统) 根据FHS标准,Linux目录有以下 ...

  10. 通过齐博fun函数实现调用每周赚取的积分排行使用

    下面代码保存到fun函数目录即可 <?php namespace app\common\fun; use think\db; class Jfrank { /** * @param string ...