一次Java应用内存泄露排查血泪史
一、故事背景
最近研究院的猴子们,支持公司的一个重点项目,遇到一个诡异的项目,一个运行在容器云上的Java应用(OA类的一个附件上传应用),JDK版本为1.8,基础镜像为ocp的tomcat8,此应用在运行一段时间后就自动重启,而且比较规律,几乎是一个小时一次,该集群其他应用正常运行,分析平台日志,仅是能看到此应用所在Pod在一直在CrashLoopBackOff,难道是被传说中的OOM砸中了?查看应用日志并没有发现有关OOM的报错,应用能看到的日志翻完也没啥有价值的信息,只能去找服务器日志了,查看主机的dmesg日志,发现Cgourp将此Java应用的进程Kill掉了,此时容器的内存Limit值为8G。报错如下图所示
上图可以发现,此java应用超过8G了,果断调大容器内存的Limit值为16G,理所当然这波肯定是没啥作用,过了几个小时内存飙到了17G,应用又被Cgroup干掉了,java真是一往无前,一计不成只能再生一计,此时想到jdk8并不能自动发现容器的限制,会不会是JVM的参数没生效呢?接着修改tomcat容器的启动脚本,设置-Xms16g Xmx16g,容器limit也设置为16g。终于Cgroup没再动杀心了,但是应用在跑一段时间后内存仍旧居高不下,并且应用也变得奇卡无比。
世上果然没有过不去的坎,一坎过去又一坎,查看日志发现会有Redis连接超时的日志,目标转向Redis,查看Redis服务端日志发现会有如下报错Client id=56506 addr=172.0.7.0:58852 fd=30 name= age=1 idle=0 flags=N db=0 sub=2 psub=1 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=2869 omem=47069133 events=rw cmd=psubscribe scheduled to be closed ASAP for overcoming of output buffer limits.发现是buffer limits超出了,修改Redis的ConfigMap配置,重启pod后问题解决。Redis正常了,应用还是卡、慢、内存爆炸!下面就是今天的重头戏了。
二、JVM内存分析
系统慢,内存高,绕也绕不过的JVM,盘它!1.首先进到Java应用所在的Pod里,jps命令找到java应用的pid
2.jmap和jstat分析堆内存各区使用情况
jmap -heap
jstat -gcutil 294 1000
发现新生代和老年代居然已经都已经100%了!应用没跑多久,jvm已经做了297次FullGC了,平均11.5s做一次FullGC,看来猫腻就在这了,所以就开始分析堆内存吧
jmap -histio
有一个io.lettuce.core的东西实例达到了3亿多实例没有回收!,问题已经定位到了,目标锁定,全军出击!Lettcue是Redis的Java客户端,怀疑是线程没有被正确释放,实例数无法销毁,升级Lettcue版本问题解决。
今天的内容就到这里了,我们下期再见~