Java详解线上内存暴涨问题定位和解决方案
前因:
因为REST规范,定义资源获取接口使用GET请求,参数拼接在url上。
如果按上述定义,当参数过长,超过tomcat默认配置 max-http-header-size :8kb
会报一下错误信息:
Request header is too large
可以修改springboot配置,调整请求头大小
server: max-http-header-size: xxx
后果:
如果max-http-header-size设置过大,会导致接口吞吐下降,jvm oom,内存泄漏。
因为tomcat 会用HeapByteBuffer 预分配请求头内存大小,在堆上分配。
请求和响应都是一样的配置,每次请求处理预先分配,2倍配置值内存大小在 jvm 堆中
请求过多,导致线上内存暴涨,老年代有3GB多。使用jmap dump线上内存数据,使用 JProfiler 分析。
符合配置大小和源码对象
数组有3GB,和老年代和eden区总和大小相近。
-XX:PretenureSizeThreshold jvm参数用来设置默认值,当数组或对象大小超过这个设定值,直接在 Old Gen 老年代分配;默认值0,当超过eden区的大小的时候,直接分配到old区。
使用 java -XX:+PrintCommandLineFlags -version
发现并没有使用
-XX:PretenureSizeThreshold参数,所以是
max-http-header-size设置过大,eden区分配不够,直接分配到old区,堆区内存不够,自动扩容,导致old区数据越来越多,频繁触发FullGC。
JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小 于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、 -Xmx相等以避免在每次GC后调整堆的大小。
其中http-nio-9005-exec-线程有146个,和前面配置需要分配的20M内存请求,相乘的数据与3000MB相近。
tomcat任务线程池 最大线程数200,存活时间60s
因为TaskQueue 重写了offer方法,在线程池大小小于最大线程数时,任务不会放入任务队列,只会交给现有线程执行;存活时间60s,只有当线程空闲60s才会被回收,也就是**60秒内请求要小于当前线程数,**才会有空闲线程。这就导致了线程不能及时被回收。请求数下降,但是内存还是居高不下。
解决方案:
max-http-header-size修改为默认值,接口请求方式修改为POST,请求参数放置于body
到此这篇关于Java详解线上内存暴涨问题定位和解决方案的文章就介绍到这了,更多相关Java 内存暴涨内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!