- 问题发现
早上休病假在家舒舒服服地睡懒觉,床都还没有起,同事就发消息来说程序CPU一直拉爆,内存使用正常,而且现在ping接口都call不进去。想到这可能是线程设得太多了,然后上游提交了一个batch任务在一直跑吧,毕竟是SIT环境而已,于是就让同事等我下午回去再说吧。
- 问题分析
去到公司准备检查这个问题,同事说已经重启了程序…
没关系,上游又调用了接口提交了个batch任务过来,再耐心等等就是了。然后果不其然又复现了问题,内存使用率稳定,CPU拉爆,接口都无法访问,赶紧找运维帮忙拿相关的信息。
# 因为是k8s, 没有权限进入aks,只能发email找运维
# 进入pod中
kubectl exec podname -n namespace /bin/bash
# 查看thread dump
jstack <pid> > stack.log
# 查看gc信息
jstat -gc <pid> > gc.log
# 查看堆信息
jhsdb jmap --heap --pid <pid> > heap.log
因为看到pod的内存使用率只有50-60%,就没有想到OOM,就没想dump内存快照。
趁着运维还没有回复,就查了一下log,惊呆了老铁,居然看到了java.lang.OutOfMemoryError: Java heap space
,但是程序还在往下跑,pod内存使用率才50-60%,居然就OOM了?好吧,确实是OOM了,于是猜测是batch任务产生了很多对象,经过gc到了老年代,把老年代了撑爆了,然后OOM这个异常是在子线程供抛出的,被主线程了catch掉了,主线程依然不断往下跑, CPU拉爆是由于老年代OOM,不停地在Full GC。
等到下班运维还不帮忙干活,那只能等第二天上班来搞了,然后千辛万苦终于拿到了这些文件。此时程序CPU使用率已经降下来,但是API仍然无法访问。
YGC YGCT FGC FGCT
312 2.491 39165 14587.956
果然如猜想的一样,因为频繁Full GC导致的CPU拉爆,但是有些不对劲,怎么young GC次数怎么比Full GC次数少这么多次?再看堆内存信息。
S0C S1C S0U S1U EC EU OC OU
6528.0 6528.0 0.0 981.2 52480.0 20750.0 131072.0 86225.4
乍一看没什么毛病,但是仔细看是有些问题的,整个堆的大小总共才192M,所以在监控页面看到pod的内存使用率才50-60%,但是程序已经OOM了。
这是因为我们没有设置堆内存大小,使用的是默认值,整个pod的内存大小为768m, 最大堆大小为768 / 4 = 192m。
最大堆大小:物理内存小于192MB时,为物理内存的一半;物理内存大于192MB且小于1GB时,为物理内存的四分之一;大于等于1GB时,都为256MB
初始化堆大小:至少为8MB;物理内存大于512MB且小于1GB时,为物理内存的六十四分之一;大于等于1GB时,都为16MB
至于为什么young GC次数这么少?
是因为新生代太小了,Eden区很快爆满,Survivor区又不足以存放存活的对象,所以直接进去老年代了。
知道原因后调大堆内存空间,本地进行测试没有问题。
-Xms512m
-Xmx512m
-Xmn128m
-XX:MetaspaceSize=32M
-XX:MaxMetaspaceSize=64M
至于在CPU和内存都恢复正常的情况下,为什么API接口还是无法调用?
thread dump显示没有死锁,tomcat线程也都是在waiting状态。
也并非nginx把程序当作已宕机,直接通过IP也无法访问,至此没有任何头绪,只能先重启程序,再慢慢研究。