您好,这里是「码农镖局」掘金小站,欢迎您来,欢迎您再来~
除了Tomcat、Jetty,另一个常见的可能出现OOM的地方就是微服务架构下的一次RPC调用过程中。笔者曾经经历过的一次OOM就是基于Thrift框架封装出来的一个RPC框架导致的宕机。
也就是当服务A更新后,服务B宕机了。
通过查看GC日志,发现是JVM堆抛出的OOM。打开内存快照,发现超大byte[]数组,而这个超大的byte[]数组是由RPC框架持有的。
初步判定原因是:服务A修改了Request类,但服务B未更新该类,还是旧版本,因此导致反序列化失败时RPC会开辟一个byte[]数组,默认大小是4G。
因此最终的解决方案也很简单:
1、服务B更新Request类;
2、将RPC默认byte[]数组大小调整为4M。
另一次事故是由马虎的开发工程师引起的。某个马虎的工程师用mybatis写的SQL语句在某些情况下允许不加where条件就可以执行,这导致一下子查出来上百万条数据,引发系统OOM。这种情况下,MAT工具对由Web容器(Tomcat/Jetty)或者RPC等底层框架所引发的OOM故障,用处并不大。但如果OOM主要是由于业务代码导致,那使用起来就简单得多。使用MAT工具定位问题的时候:
1、利用histogram功能占用内存最多的对象
2、找到占用内存过多的对象,并深入看看对象之间的持有关系
3、找到问题代码
与前面的RPC引发的OOM类似,有一个线上数据同步系统,专门从另一个系统同步数据,通过kafka来发送与消费数据。即使是这么简单的一个系统,也会不时地报一个OOM错误,且频率越来越高。这难道又是工程师的粗心引起的吗?
通过简单分析可以知道,既然每次重启过后都会频繁出现OOM,就说明内存使用率会不断上涨且难以清理。而JVM出现OOM,一般都是由两种情况引起:
1、要么高并发高负载,一瞬间创建太多对象,导致内存放不下
2、要么存活对象太多,GC无法回收
而针对此案例,极有可能就是第二种情况:存活对象太多。
这一点可以通过jstat来验证。重启系统后,启动jstat,观察到:
1、老年代持续增长,且老年代使用率达到100%后,Full GC根本回收不掉任何对象
2、老年代维持一段时间的100%使用率后,发生OOM
同时通过MAT找到占用内存最多的对象。利用Histogram功能,最终发现问题原因:
1、kafka的吞吐量和数据库的吞吐量完全不是一个数量级;
2、数据库的处理能力慢,导致数据不断积压,最终内存溢出;
3、典型的没控制好生产与消费数据的速率导致的OOM。
最后总结一下这几个案例:
1、必须对线上系统使用的各种技术,从服务器框架,到第三方框架,到Tomcat/Jetty等Web服务器,再到各种底层的中间件系统,都有深入的理解;
2、一般线上系统的故障,极少数是由业务代码直接导致的——更一般的情况是某个开源技术的内核代码就可能存在一定的故障和缺陷,只是在某些极端场景下才被发现而已;
3、定位、解决OOM问题,需要仔细研究底层源码,再结合线上业务、系统负载、参数配置和系统日志(含GC日志)等多种手段,才能完成;
4、一般情况下禁止显式调用System.gc(),但是如果开发的是文件存储、IM等大量使用NIO技术的应用,就不要禁止System.gc(),但业务代码还是不要出现System.gc();
5、生产环境一不使用parallel垃圾回收器;
6、一个Tomcat就是一个进程,不管在上面部署了多少个应用,都只有一个JVM进程。
感谢您的大驾光临!欢迎骚扰,不胜荣幸~