新上线的一个项目,运行了半个月,突然出现频繁宕机(有大量的第三方接口请求业务),严重影响了业务系统的使用。
Linux (3.10.0-957.el7.x86_64)
open JDK 1.8.32
SpringBoot 2.2.6.RELEASE
内嵌 Tomcat 9.0.47
配置最大堆内存配置 16G
通过对日志检查,发现有下面的报错信息,从错误日志看,是由于出现了OOM错误,堆内存溢出导致的问题。
Exception in thread "http-nio-8080-exec-1011" java.lang.OutOfMemoryError: Java heap space
Exception in thread "http-nio-8080-exec-1031" java.lang.OutOfMemoryError: Java heap space
Exception in thread "http-nio-8080-exec-1052" java.lang.OutOfMemoryError: Java heap space
由于不知道程序是哪一部分的代码或者配置出现的问题,所以计划模拟OOM场景,并导出dump文件进一步分析。
-Xms1024M -Xmx1024M -XX:+HeapDumpOnOutOfMemoryError
通过JMeter工具,模拟生产环境,开启十个线程发送1000笔请求同时访问接口,发现复现了以上的OOM,并在项目根目录生成了内存快照java_pid4521.hprof。
使用VisualVM工具打开,发现20个实例竟然占据了98%的大小,其中byte数组中占用了大量的资源,展开细看发现有很多Http11InputBuffer和Http11OutputBuffer实例,经查阅资料得知,均是关于网络请求缓存一类的。
Http11InputBufferHttp11OutputBuffer
org.apache.coyote.http11.Http11Processor#Http11Processor
public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
super(adapter);
this.protocol = protocol;
httpParser = new HttpParser(protocol.getRelaxedPathChars(),
protocol.getRelaxedQueryChars());
inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpHeaderSize(),
protocol.getRejectIllegalHeaderName(), httpParser);
request.setInputBuffer(inputBuffer);
outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpHeaderSize());
response.setOutputBuffer(outputBuffer);
org.apache.coyote.http11.AbstractHttp11Protocol
/**
* Maximum size of the HTTP message header.
*/
private int maxHttpHeaderSize = 8 * 1024;
public int getMaxHttpHeaderSize() { return maxHttpHeaderSize; }
经查看源码得知,tomcat对于inputBuffer和outputBuffer的HTTP请求header大小,初始化默认都是8 * 1024。
在代码里进行了全局检索,终于发现了类似于header的配置:server.max-http-header-size,设置的为102M。一次inputBuffer就会有一次outputBuffer,即每次请求相当于占用了双倍,没及时释放就会积累,况且JVM分配的内存和本身物理机器空间也不是无限的,当请求数量过多,必然会出现内存溢出的问题,到此,基本已经确定了,就是这个不合理的最大http请求头参数导致的问题。
我们把如下配置文件中的参数去掉,保留默认的配置,进行压力测试后,发现没有出现过OOM现象,自此该问题得以解决。
> server.max-http-header-size=102400000