tomcat 8.5 中,默认 maxHttpHeaderSize = 8KB
https://tomcat.apache.org/tomcat-8.5-doc/config/http.html
一、OOM 快照分析
JVM 程序启动指定参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xxx
指定OOM时打印快照,以及快照存储的路径。
获取到 OOM 快照后,通过 eclipse memory analyzer
进行分析。
载入快照后对应的饼图如下:
如图,mat
标记了可能问题的两个地方,命名为
- Problem Suspect 1
- Problem Suspect 2
各自来看看这个两个问题点
1.1、Problem Suspect 1
如图,可以知道三个关键的信息点:
- 1、存在问题的接口
/address/selectByCoordinate
- 2、
byte[]
占用的空间都是 9.8M - 3、占用空间的时
tomcat
线程
1.2、Problem Suspect 2
如图,同样可以知道三个关键的信息点:
- 1、存在问题点
Http11OutputBuffer
- 2、
byte[]
占用的空间都是 9.8M - 3、占用空间的时
tomcat
线程(Http11OutputBuffer
属于 tomcat 内容)
1.3、判断分析
通过上述两个问题点,排查的大方向有两个
- 1、tomcat 相关配置存在问题导致,同时该配置应该是配置的:9.8M(10276044.8)左右的配置。最终找到了
**server.max-http-header-size**=**10240000**
配置(约等于9.76M)。 - 2、
/address/selectByCoordinate
可能存在问题
排查得知,该接口并没有特殊处理,且没有大对象处理。
不过该地址的平时并没有高频请求。突然存在洪峰,QPS到 1000多。
该情况运维排除了第三方爬虫原因,由于业务问题,可能存在突然的流量访问。
除了上述两个基础信息结论,还有服务具体配置信息
- max-http-header-size=10240000
- server.tomcat.max-threads=200
- -Xmx 1024m
1.4、结论
请求头设置过大,加上高频请求,tomcat 线程打满,导致内存不够分配,最后内存溢出。
(无论在那种场景下,都应该设置合理的 header size,不合理的配置,会导致服务性能使用率降低,因为大多数场景下并不需要配置那么大的值,在 8.5x 及 9.x 都默认 8K。),1.5、场景还原
出现问题对应的 tomcat 为8.5 ,还原成 tomcat 对应的是 9.0.39 。
虽然版本不一样,但是不影响场景还原测试,相关配置都是一致的
1.5.1、环境
oom-case.7z.txt
Spring Boot 2.1.18(对应 tomcat 9.0.x)
对应配置
- tomcat 最大线程数 200
- max-http-header-size = 10240 KB = 10M
- -Xmx 1024m
1.5.2、单请求查看 header buffer 大小
提供接口,并打端点,接口如下:
端点数据如下:
通过换算得到:10485760字节 = 10M 【通过修改 max-http-header-size 大小能够观察变化】
1.5.2、压测模拟内存溢出
通过 Jmeter5.4.1 进行压测。 当前每个请求头占用约 10M ,当前 tomcat 处理上线 200个线程。即最大占用内存200 * 10 约等于 2G 当前 JVM 内存空间 1G。 即:理想状态下当前服务处理请求时,服务响应快速,线程快速处理,所以tomcat 线程都能够及时处理。
- 模拟100 个瞬时流量
10 M * 100 =1000 M 接近 !G 。加上JVM 程序本身消耗的内存,直接内存溢出。
二、解决方案
压测时,先预热、或者 tomcat 初始线程加大,提高压测准确率。
2.1、合理的 max-http-header-size 大小(推荐)
tomcat(8.5.x、9.0.x)默认 max-http-header-size=8KB
。
非必要不去调整 max-http-header-size
大小,即使调整也应该合理调整,而不是为了一劳永逸直接加了10M 之类的大配置。
这里将 max-http-header-size
设置为32KB 进行压测测试,模拟 200 个瞬时并发(不贴图)。
即使模拟上千个瞬时并发也不再出现溢出问题(忽略 tomcat 最大线程配置、业务处理)
2.2、增加 JVM 内存大小
2.3、降低 tomcat 线程数上线(不推荐)
略