记虎牙视频弹幕JVM故障排查与解决
✔背景
3月份在做一个虎牙视频弹幕的项目,完成设计和开发后进行功能测试没有什么问题,因为这个项目弹幕的获取和新增都是去调用虎牙那边的thrift服务,压力主要还是在他们那边,加之产品经理着急项目赶紧上线,所有没有进行压力测试,功能测试OK了就赶紧发布了。
项目上线进行一些功能回归测试是没有问题的,一段时间后,测试同学和产品发现线上的弹幕列表没办法获取,新增弹幕也失败。
✔排查
我赶紧打开浏览器请求一下相关接口,没想到直接超时了。
然后进去服务器,看了一下,并没有明显的报错日志;我在想会不会是java经常crash了,用了
1 | ps -ef|grep java |
查看了一下,java进程(tomcat)是在的,那为什么接口访问直接超时呢,是不是java进程已经假死了。
我赶紧用jstat看一下java进程的GC情况:
1 | jstat -gcutil pid |
看了一下,进程基本没有GC发生,Perm space(永久代)占了95%+。
开始,我觉得是不是老年代设得太小导致了,full GC频繁导致的???那为什么不会出现OOME,我把内存都调大了一倍左右,Permentent Space也增加了一倍左右。
然后用ab进行压力测试,发现刚开始就是Young GC比较频繁(这个正常,因为正在压力测试,young GC频繁正常),Full GC并不是非常频繁,Permentent Space 也一直稳定在一半大小以下(因为比之前调大了一倍啦)。
那这TM就奇怪了。。。
。。。。。。
我还是不死心一直怀疑是java进程的内存设置有问题,然后我又用jmap -histo 打印每个class的实例数目,内存占用,类全名信息。
1 | jmap -histo pid |
看了一下,也没有那个类的数目非常地多,或者占用了非常多的内存。
。。。
从JVM的内存情况实在TMD看不出个所以然,那就只能看一下线程堆栈了,赶紧把线程堆栈打印出来看看。
1 | jstack -f pid |
终于从线程堆栈可以看出点端倪来。
我们可以看出
1 | getDanmuList() |
这个获取弹幕列表方法阻塞了,接着看一下堆栈里面调用了多少次这个方法:
我们可以看出这个方法调用了150次之多,而且这150条线程都处于阻塞状态,这么多次阻塞,直接导致整个web服务器进程假死了。。。
看一下代码:
1 | public List<DanmuVO> getDanmuList(long vid) { |
逻辑主要是获取一个thrift的连接,然后去调用服务化的接口,我们可以清楚地看到代码的最后并没有关闭连接。
因为每次调用服务化接口,需要打开一个连接,但是调用完了并没有关闭,调用一多,连接池就没有连接可用了,后面调用服务化接口的请求就一直在等待,最后导致大量线程阻塞了,直接造成整个tomcat假死了。
✔解决
代码加上关闭连接的操作:
1 | public List<DanmuVO> getDanmuList(long vid) { |
然后在用ab压测了一下,然后在看一下线程堆栈情况,终于恢复正常了。
✔教训
- 无论项目使用人数多少,都要进行压力测试。
- 应用有外部系统调用,要记得有相关的连接关闭操作。
- 应用有IO操作,也要有相关的IO关闭操作。