容器中Java 程序OOMKilled原因浅析

背景:

业务的容器化刚刚搞完,线上开始告警,容器重启,容器重启。describe pod 查看原因是OOMKilled

分析:

OOMKilled 是pod 中的进程使用的内存超过了.spec.containers[*].resources.limits.memory中定义的内存限制,在超出限制后, kubernetes 会向容器中的进程(pid=1)发送kill -9 信号。kill -9 信号对于进程来说是不可捕捉的,进程无法在收到-9 信号后优雅的退出。 这对于业务来说是有损的。那么为啥进程会超过容器的limit 限制呢?
查看容器中进程的启动参数:

java -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Shanghai -XX:MetaspaceSize=128m -jar bxr-web-1.0.jar

查看容器的limit限制

k8s-master-01#kubectl get pods -n calculation bxr-web-dd656458b-8m4fb -o=custom-columns=name:.metadata.name,namespace:.metadata.namespace,memory-limit:.spec.containers[0].resources.limits.memory

name                      namespace     memory-limit
bxr-web-dd656458b-8m4fb   calculation   2000Mi

进程没有设置内存限制,但是这个业务之前在虚拟机上运行时,配置相同,启动参数也是如此,为什么上线到容器中会经常出现OOMKilled 的情况呢。这里就需要说到docker对进程资源的限制。

docker 通过 cgroup 来控制容器使用的资源配额,包括 CPU、内存、磁盘三大方面,基本覆盖了常见的资源配额和使用量控制。但是在java 的早期版本中(小于1.8.131),不支持读取cgroup的限制。 默认是从/proc/目录读取可用内存。但是容器中的/proc目录默认是挂载的宿主机的内存目录。即java 读取的到可用的内存是宿主机的内存。那么自然会导致进程超出容器limit 限制的问题。
验证:

起初, 我们采用为进程设置-Xmx参数来限制进程的最大heap(堆)内存。例如。 容器的limit限制为3G。 那么设置java进程的最大堆内存为2.8G,采用这种方式后,容器重启的情况少了很多,但还是偶尔会出现OOMKilled 的情况。因为-xms 只能设置java进程的堆内存。 但是其他非堆内存的占用一旦超过预留的内存。还是会被kubernetes kil掉。附java 内存结构:

JVM内存结构主要有三大块:堆内存、方法区和栈

堆内存是JVM中最大的一块由年轻代和老年代组成,而年轻代内存又被分成三部分,Eden空间、From Survivor空间、To Survivor空间,默认情况下年轻代按照8:1:1的比例来分配;

方法区存储类信息、常量、静态变量等数据,是线程共享的区域,为与Java堆区分,方法区还有一个别名Non-Heap(非堆);

栈又分为java虚拟机栈和本地方法栈主要用于方法的执行。

那么有没有办法能让java 正确识别容器的内存限制呢?这里有三种方法:

  1. 升级java版本。Java 10支持开箱即用的容器,它将查找linux cgroup信息。这允许JVM基于容器限制进行垃圾收集。默认情况下使用标志打开它。
-XX:+UseContainerSupport

值得庆幸的是,其中一些功能已被移植到8u131和9以后。可以使用以下标志打开它们。

-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap
  1. LXCFS,FUSE filesystem for LXC是一个常驻服务,它启动以后会在指定目录中自行维护与上面列出的/proc目录中的文件同名的文件,容器从lxcfs维护的/proc文件中读取数据时,得到的是容器的状态数据,而不是整个宿主机的状态。 这样。java进程读取到的就是容器的limit 限制。而不是宿主机内存
  2. -XX:MaxRAM=`cat /sys/fs/cgroup/memory/memory.limit_in_bytes` 通过MaxRAM 参数读取默认的limit限制作为java 内存的最大可用内存。同时结合-Xmx 设置堆内存大小


容器中Java 程序OOMKilled原因浅析

原文地址:https://www.cnblogs.com/itanony/p/11037470.html

时间: 2024-09-30 05:26:45

容器中Java 程序OOMKilled原因浅析的相关文章

如何设置Docker容器中Java应用的内存限制

如果使用官方的Java镜像,或者基于Java镜像构建的Docker镜像,都可以通过传递 JAVA_OPTS 环境变量来轻松地设置JVM的内存参数.比如,对于官方Tomcat 镜像,我们可以执行下面命令来启动一个最大内存为512M的tomcat实例 docker run --rm -e JAVA_OPTS='-Xmx512m' tomcat:8 在日志中,我们可以清楚地发现设置已经生效 "Command line argument: -Xmx512m" 02-Apr-2016 12:46

Linux中java程序的部署,开机自启动(一)

Linux系统中 需求: (1)使用.sh文件控制java程序的启动.停止.重启.查看状态 需求升级: (2)将java程序部署为开机自动启动,使用service hello status/stop等模式控制程序的运行情况 我是参考下面几篇文章实现的 Linux中部署JAVA程序 http://www.linuxidc.com/Linux/2013-09/90673.htm 设置Linux自启动服务 http://just4java.iteye.com/blog/474392 =========

Linux中java程序的部署,开机自启动(二)

这是从网上看到的,没有实践过,不知道是否容易部署,留作以后有时间再研究 貌似都是使用同一种方式,到时可以参考比较 使用Java Service Wrapper将java程序作为linux服务并且开机自动启动 http://www.blogjava.net/shufudong/articles/283241.html 使用Java Service Wrapper将Java程序发布成Windows Service http://www.cnblogs.com/Cindy_weiwei/archive

phpStorm中使用xdebug工具调试docker容器中的程序

前提准备 phpstorm开发软件 + dnmp(docker + nginx + mysql +php) 配置好hosts 映射比如 /etc/hosts      127.0.0.1 tp5.dev 为现有的php环境安装好xdebug扩展,安装成功之后可以通过页面输出phpinfo()查看是否安装成功xdebug 安装能够进行调试的ieda环境,一般对于php程序开发者来说,使用phpStorm较多,本文也是通过这个讲述. 成熟的docker构建的环境. 可以使用https://githu

二、第一个JAVA程序——HelloWorld

1.Java程序的运行机制. Java源文件的后缀名为java 形如 *.java,通过编译器编译后生成*.class文件,由计算机来运行class文件执行java程序.但是,此时所指的计算机并不是物理上的计算机而是java的虚拟机,即JVM. Java中所有的程序都是运行在JVM上的,即所有的*.class文件在JVM上运行而对于*.class文件来说只需要符合JVM的规范就可以了,而不需要考虑对硬件或者其他操作系统的兼容性.再由JVM去适应各个操作系统,因此,有了java的一句口号一次编译,

理解 docker 容器中的 uid 和 gid

默认情况下,容器中的进程以 root 用户权限运行,并且这个 root 用户和宿主机中的 root 是同一个用户.听起来是不是很可怕,因为这就意味着一旦容器中的进程有了适当的机会,它就可以控制宿主机上的一切!本文我们将尝试了解用户名.组名.用户 id(uid)和组 id(gid)如何在容器内的进程和主机系统之间映射,这对于系统的安全来说是非常重要的.说明:本文的演示环境为 ubuntu 16.04(下图来自互联网). 先来了解下 uid 和 gid uid 和 gid 由 Linux 内核负责管

在 docker 容器中捕获信号

原文:在 docker 容器中捕获信号 我们可能都使用过 docker stop 命令来停止正在运行的容器,有时可能会使用 docker kill 命令强行关闭容器或者把某个信号传递给容器中的进程.这些操作的本质都是通过从主机向容器发送信号实现主机与容器中程序的交互.比如我们可以向容器中的应用发送一个重新加载信号,容器中的应用程序在接到信号后执行相应的处理程序完成重新加载配置文件的任务.本文将介绍在 docker 容器中捕获信号的基本知识. 信号(linux) 信号是一种进程间通信的形式.一个信

转载--编写高质量代码:改善Java程序的151个建议(第1章:JAVA开发中通用的方法和准则___建议16~20)

阅读目录 建议16:易变业务使用脚本语言编写 建议17:慎用动态编译 建议18:避免instanceof非预期结果 建议19:断言绝对不是鸡肋 建议20:不要只替换一个类 回到顶部 建议16:易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP,Ruby,Groovy.Javascript等,这些入侵者都有一个共同特征:全是同一类语言-----脚本语言,它们都是在运行期解释执行的.为什么Java这种强编译型语言会需要这些脚本语言呢?那是因为脚本语言的三大特征,如下所示:

Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)

1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题.(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对象都在堆(Heap)中分配空间. 释放:对象的释放是由垃圾回收机制决定和执行的,这样做确实简化了程序员的工作.但同时,它也加重了JVM的工作.因为,GC为了能够正确释放对象,GC必须监控每一个对象的运行状态,包括对象的申请.引用.被引用.赋值等,GC都需要进行监控. 2.什么叫java的内存泄露 在