Java应用被k8s认定为oom杀掉

Java应用被k8s认定为oom杀掉

前不久现场反馈说服务运行一段时间就重启了,希望我介入排查一下。

先说结论

jvm堆大小与k8s pod设置的大小一致,均为4g。因jvm还存在其他的内存占用,pod服务总体的内存占用会超过4g,k8s认定为oom,将其杀掉。以及jvm较低版本没有支持容器namespace的资源隔离。

处理过程

  1. 检查日志

    看服务重启前后日志有无抛出一些较为严重的错误,是否存在因为个别异常导致的服务重启;

  2. 修改jvm堆的大小,增加oom时候自动dump等参数

    1. 怀疑是系统内部有一些“不良”业务导致的堆的大量占用,试想是否可以通过增加堆的大小,再对堆进行快照分析,确定具体占用较大堆内存的业务代码,对其进行定向优化。
    2. 增加-XX:+HeapDumpOnOutOfMemoryError以及-XX:HeapDumpPath=/myPath/heapdump.hprof参数,让jvm在下一次oom时自动导出堆的快照,便于分析。

    通过不停的分析堆的镜像快照,确实是没有任何业务代码过多的或者过量的导致了堆的增长,增加的-XX:+HeapDumpOnOutOfMemoryError也没有生效。

  3. 检查k8s pod信息

    在k8s
    pod信息查到,服务是以oom的原因导致被kill的。经过了解,k8s认定pod的内存占用达到了pod所配置的limit值时,就会判定为oom,然后杀掉,从侧面增加-XX:+HeapDumpOnOutOfMemoryError
    无效的原因。

    检查现场配置时发现,jvm堆的大小和pod的大小限制一致。已知java 8除了堆以外还有其他的内存占用,猜测是不是这部分导致了pod实际内存使用大小超过了pod限制导致。

    修改pod限制参数为6g后,问题得以解决。

后续思考

虽然修改为6g以后,服务不再被k8s以oom的原因kill了,但于此同时,还有一个问题一直萦绕着我。

为什么jvm没有回收内存?

在一番了解后,我了解到Java 1.8有一个更新,在Java 1.8
191这个版本中,Java更新了对于namespace的支持,地址是:https://www.oracle.com/java/technologies/javase/8u191-relnotes.html,对应具体的bug描述是https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115

容器是通过linux的namespace做资源隔离,通过pid下的cgroup做资源限制,但是在早先版本中,jvm并没有支持这一特性,从而导致了jvm内存不回收的问题。

jvm指定的堆大小到底