记一次OOM排查(max-http-header-size导致)

记一次OOM排查(max-http-header-size导致)

现象

有个服务考虑并发较大,在服务器内存空闲较多情况下给这个服务配置启动最大堆内存5G,但是在并发请求较大时,任然会偶现内存溢出。

排查思路:

在本地启动该服务,堆内存也设置为5G,并增加启动jvm参数(-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\error ,这个参数的含义是在oom时保存堆内存快照,并将内存快照下载到本地D盘error目录下),并发100线程请求该服务接口,当发生内存时,会保存内存快照,用MAT、java自带jvisualvm等工具分析oom时堆内存中大量存在的对象来排查具体问题。

排查过程:

在这里插入图片描述

​ 这个就是下载下来的堆内存快照

用MAT(MemoryAnalyzer)和jvisualvm打开内存快照文件

jvisualvm类目录如下:

记一次OOM排查(max-http-header-size导致)_第1张图片

如上图,当发生OOM时内存中最大的对象的byte[],占了堆内存的98%,byte[]一般是程序申请的内存空间,可能还未被具体其他对象占用,继续排查;

打开MAT,查看Top Consumers( 通过图形列出最大的object )
记一次OOM排查(max-http-header-size导致)_第2张图片
记一次OOM排查(max-http-header-size导致)_第3张图片

点击查看这些大对象:
记一次OOM排查(max-http-header-size导致)_第4张图片

发现一个http请求头的buffer占用了大约50M,刚才模拟演示时是用100个线程建立请求连接,100*50M = 5G,刚好分配的对内存被耗尽。

问题原因:

是http请求头申请内存过大,那么现在排查代码,是否有设置请求头,经查代码中有配置:

记一次OOM排查(max-http-header-size导致)_第5张图片

设置了http请求头过大,经询问同事,之前因为请求头中加了鉴权签名,签名较大故将请求头大小调大了。

经讨论,将鉴权签名改到body中,max-http-header-size这行配置删除,使用默认配置,再次重启服务,并发增大至2000服务也正常。

总结及扩扩展:

扩展1:max-http-header-size默认值是多少?

在项目依赖的springboot版本包中找到配置文件:

记一次OOM排查(max-http-header-size导致)_第6张图片

在其中找到max-http-header-size配置:

记一次OOM排查(max-http-header-size导致)_第7张图片

这里的意思是server.tomcat.max-http-header-size配置过时了,现在用的是server.max-http-header-size,那么找到server.max-http-header-size配置:

记一次OOM排查(max-http-header-size导致)_第8张图片

所以,默认请求头大小是8K

扩招2:为什么默认8k不会导致内存溢出,而设置50M会导致内存溢出?

服务用的是java8,使用的是默认垃圾回收器ParalleGC,采用的是分代回收,新生的对象会先存入新生代,但是对于50M的大对象,新生代很快就没有足够的内存去分配,那之后的50M大对象都会直接存在老年代,当老年代内存不足,触发full GC,而http连接断开之前,这些大对象都无法被回收,所以查看GC日志可以发现,在频繁Full GC(jvm在进行濒死挣扎)后,内存还是不足,那么久会报出内存溢出

而默认8K配置,所需内存较小,新生代有足够的内存去分配,新生代中的垃圾对象,一般只需要minor GC就可清除,从而不会出现内存溢出。

扩招3:启示
  1. 服务参数配置不能仅仅头疼医疼,并发及性能很重要
  2. 系统中要避免过大的对象放入内存,因为过大内存会直接放入老年代,如并发再一大,易造成内存溢出。

你可能感兴趣的:(java,http,jvm,java)