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 进行分析。

载入快照后对应的饼图如下:

image.png

如图,mat标记了可能问题的两个地方,命名为

  • Problem Suspect 1
  • Problem Suspect 2

各自来看看这个两个问题点

1.1、Problem Suspect 1

image.png

image.png

image.png
如图,可以知道三个关键的信息点:

  • 1、存在问题的接口 /address/selectByCoordinate
  • 2、byte[]占用的空间都是 9.8M
  • 3、占用空间的时 tomcat线程

1.2、Problem Suspect 2

image.png
image.png

image.png

如图,同样可以知道三个关键的信息点:

  • 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 大小

提供接口,并打端点,接口如下:
image.png
端点数据如下:

image.png
通过换算得到:10485760字节 = 10M 【通过修改 max-http-header-size 大小能够观察变化】

1.5.2、压测模拟内存溢出

通过 Jmeter5.4.1 进行压测。 当前每个请求头占用约 10M ,当前 tomcat 处理上线 200个线程。即最大占用内存200 * 10 约等于 2G 当前 JVM 内存空间 1G。 即:理想状态下当前服务处理请求时,服务响应快速,线程快速处理,所以tomcat 线程都能够及时处理。

  • 模拟100 个瞬时流量
    image.png
    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 线程数上线(不推荐)