上周运维反馈线上程序出现了OOM,程序日志中的输出为

Exception in thread "http-nio-8080-exec-1027" java.lang.OutOfMemoryError: Java heap space
Exception in thread "http-nio-8080-exec-1031" java.lang.OutOfMemoryError: Java heap space

看线程名称应该是tomcat的nio工作线程,线程在处理程序的时候因为无法在堆中分配更多内存出现了OOM,幸好JVM启动参数配置了-XX:+HeapDumpOnOutOfMemoryError,使用MAT打开拿到的hprof文件进行分析。

第一步就是打开Histogram看看占用内存最大的是什么对象:



可以看到byte数组占用了接近JVM配置的最大堆的大小也就是8GB,显然这是OOM的原因。

第二步看一下究竟是哪些byte数组,数组是啥内容:



可以看到很明显这和HTTP请求相关,一个数组大概是10M的大小。

第三步通过查看GC根查看谁持有了数组的引用:



这符合之前的猜测,是tomcat的线程在处理过程中分配了10M的buffer在堆上。至此,马上可以想到一定是什么参数设置的不合理导致了这种情况,一般而言tomcat不可能为每一个请求分配如此大的buffer。

第四步就是检查代码里是否有tomcat或服务器相关配置,看到有这么一个配置:

max-http-header-size: 10000000

至此,基本已经确定了八九不离十就是这个不合理的最大http请求头参数导致的问题。

到这里还有3个疑问:

  1. 即使一个请求分配10M内存,堆有8GB,难道当时有这么多并发吗?800个tomcat线程?
  2. 参数只是设置了最大请求头10M,为什么tomcat就会一次性分配这么大的buffer呢?
  3. 为什么会有如此多的tomcat线程?感觉程序没这么多并发。

先来看问题1,这个可以通过MAT在dump中继续寻找答案。

可以打开线程视图,搜索一下tomcat的工作线程,发现线程数量的确很多有401个,但是也只是800的一半:



再回到那些大数组的清单,按照堆分配大小排序,往下看:



可以发现除了有10008192字节的数组还有10000000字节的数组,查看引用路径可以看到这个正好是10M的数组是output buffer,区别于之前看到的input buffer:



好吧,这就对了,一个线程分配了输入输出两个buffer,占用20M内存,一共401个线程,占用8GB,所以OOM了。

还引申出一个问题为啥有这么多工作线程,

再来看看问题2,这就需要来找一下源码了,首先max-http-header-size是springboot定义的参数,查看springboot代码可以看到这个参数对于tomcat设置的是MaxHttpHeaderSize:



然后来看看tomcat源码:



进一步看一下input buffer:



buffer大小是MaxHttpHeaderSize+ReadBuffer大小,后者默认是8192字节:

   <attribute name="socket.appReadBufSize" required="false">
<p>(int)Each connection that is opened up in Tomcat get associated with
a read ByteBuffer. This attribute controls the size of this buffer. By
default this read buffer is sized at <code>8192</code> bytes. For lower
concurrency, you can increase this to buffer more data. For an extreme
amount of keep alive connections, decrease this number or increase your
heap size.</p>
</attribute>

这也就是为什么之前看到大量的buffer是10008192字节的。显然还有一批内容是空的10000000字节的buffer应该是output buffer,来看看源码:



嗯这是一个header buffer,所以正好是10000000字节。

至于问题3,显然我们的应用程序是配置过最大线程的(查看配置后发现的确,我们配置为了2000,好吧有点大),否则也不会有401个工作线程(默认150),如果当时并发并不大的话就一种可能,请求很慢,虽然并发不大,但是因为请求执行的慢就需要更多线程,比如TPS是100,但是平均RT是4s的话,就是400线程了。这个问题的答案还是可以通过MAT去找,随便看几个线程可以发现很多线程都在等待一个外部服务的返回,这说明外部服务比较慢,去搜索当时的程序日志可以发现有很多"feign.RetryableException: Read timed out executing的日志"。。。。追杀下游去!慢点,我们的feign的timeout也需要再去设置一下,别被外部服务拖死了。

记一次OOM问题排查过程的更多相关文章

  1. 记一次oom问题排查

    大家好,我是大彬~ 今天给大家分享最近出现的OOM问题. 上周五早上,测试同学反馈测试环境的子系统服务一直超时,请求没有响应. 收到这个问题之后,我有点纳闷,最近这个系统也没有改动代码逻辑,怎么会突然 ...

  2. 解Bug之路-记一次中间件导致的慢SQL排查过程

    解Bug之路-记一次中间件导致的慢SQL排查过程 前言 最近发现线上出现一个奇葩的问题,这问题让笔者定位了好长时间,期间排查问题的过程还是挺有意思的,正好博客也好久不更新了,就以此为素材写出了本篇文章 ...

  3. 解Bug之路-记一次存储故障的排查过程

    解Bug之路-记一次存储故障的排查过程 高可用真是一丝细节都不得马虎.平时跑的好好的系统,在相应硬件出现故障时就会引发出潜在的Bug.偏偏这些故障在应用层的表现稀奇古怪,很难让人联想到是硬件出了问题, ...

  4. 记一次生产环境Nginx日志骤增的问题排查过程

    摘要:众所周知,Nginx是目前最流行的Web Server之一,也广泛应用于负载均衡.反向代理等服务,但使用过程中可能因为对Nginx工作原理.变量含义理解错误,或是参数配置不当导致Nginx工作异 ...

  5. JAVA本地环境启动OOM问题排查

    1.问题描述 每次启动信息报错如下: 2.排查过程 2.1起初怀疑是堆内存不够: 将本地队内存调整由-Xms512M,-Xmx1024M,改成与测试环境相同1536M,还是失败 如上图报错中有noti ...

  6. 记一次NoHttpResponseException问题排查

    上传文件程序会有一定的概率提示错误,错误率大概在1%以下,错误信息是:org.apache.http.NoHttpResponseException , s3-us-west-1.amazonaws. ...

  7. 一次kibana服务失败的排查过程

    公司在kubernetes集群上稳定运行数月的kibana服务于昨天下午突然无法正常提供服务,访问kibana地址后提示如下信息: 排查过程: 看到提示后,第一反应肯定是检查elasticsearch ...

  8. 坑爹坑娘坑祖宗的87端口(记一次tomcat故障排查)

    原贴如下 坑爹坑娘坑祖宗的87端口(记一次tomcat故障排查) 虽然我用的是PHPstudy部署的dedecms,还是一样栽倒这个坑里了. 总结经验:本地测试使用8000~9000的端口比较安全.

  9. 记一次数据库调优过程(IIS发过来SQLSERVER 的FETCH API_CURSOR语句是神马?)

    记一次数据库调优过程(IIS发过来SQLSERVER 的FETCH API_CURSOR语句是神马?) 前几天帮客户优化一个数据库,那个数据库的大小是6G 这麽小的数据库按道理不会有太大的性能问题的, ...

随机推荐

  1. boost库 bind/function的使用

    Boost::Function 是对函数指针的对象化封装,在概念上与广义上的回调函数类似.相对于函数指针,function除了使用自由函数,还可以使用函数对象,甚至是类的成员函数,这个就很强大了哈 # ...

  2. CodeForces - 896D :Nephren Runs a Cinema(卡特兰数&组合数学---比较综合的一道题)

    Lakhesh loves to make movies, so Nephren helps her run a cinema. We may call it No. 68 Cinema. Howev ...

  3. EF各版本增删查改及执行Sql语句

    自从我开始使用Visual Studio 也已经经历了好几个版本了,而且这中间EF等框架的改变也算是比较多的.本篇文章记录下各个版本EF执行Sql语句和直接进行增删查改操作的区别,方便自己随时切换版本 ...

  4. swing之flowlayout

    import java.awt.FlowLayout; import javax.swing.JButton; import javax.swing.JFrame; //1.继承 JFrame类 // ...

  5. Zabbix通过SNMPv2监控DELL服务器的硬件信息

    (一)zabbix监控DELL服务器 (1)简述:监控DELL服务器硬件一般有两种途径:1.操作系统上安装OMSA,编写脚本调用omreport命令进行监控(需要在操作系统上安装比较麻烦):2.使用i ...

  6. Mybatis代码学习

    Mybatis架构学习 MyBatis 是支持定制化 SQL.存储过程以及高级映射的持久层框架.MyBatis 封装了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.可以对配置和原生Map使用 ...

  7. [转]ubuntu 网络配置 作者:Yudar

    检查网络配置命令:ifconfig 一.通过配置文件配置 新手没怎么用过Ubuntu,所以走了不少弯路,网上找了很多方法,大都没对我起到帮助作用,所以把自己的配置方法写一写. Ubuntu上连了两块网 ...

  8. vector向量容器元素排序与查找

    1.利用标准库函数sort()对vector进行排序 参考源码: #include <algorithm> #include <vector> vector<int> ...

  9. 数据库:ubantu下MySQL数据库备份方法

    1.编辑/etc/crontab文件设定定时任务,在制定时间执行backup_databases.sh vi /etc/crontab # /etc/crontab: system-wide cron ...

  10. linux yum 脚本实现

    yum 位于linux /usr/bin/yum yum命令是python脚本进行编写的(python 2.6) #!/usr/bin/python2.6 import sys try: import ...