栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

记一次生产环境maxHttpHeaderSize参数引发的OOM异常

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

记一次生产环境maxHttpHeaderSize参数引发的OOM异常

生产环境的一个子服务, 在并发量并不大的情况下, 也频繁出现OOM. 
我们找到其中一个发生OOM的接口, 发现并没有大量new对象的异常行为, 仅仅返回一个静态的Map中的values

for (FunctionDesc desc : funcDescriptions.values())
{
    voList.add(new FunctionVo(desc.getSignature(), desc.getDescription()));
}
return builder.success().data(voList).build();

本地调试上面的代码并没有什么大的问题, 于是写了一个测试接口test, 设置最大堆内存为1G. 

-Xms1g -Xmx1g
@RequestMapping("test")
public String test()
{
    return "ok";
}

test接口仅仅响应"ok"一个字符串, 结果并发量只有50的时候, 一样OOM.
行走江湖这么多年, 还是头一次遇见这样诡异的OOM, 但至少可以判定:OOM并不是发生在我们自己写的业务代码, 但是这才令人头疼的地方, 因为根本不知道从哪下手进行优化.
于是借助JDK自带的JvisualVM工具, 查找问题所在.
应用启动, 先查看堆内存, 堆内存在1G左右, 初始时并没有异常.

 打开Jmeter, 设置请求线程数为50并运行

这时候再回过头来看内存情况, 发现GC频繁, 且老年代只增不减, 应用也报了OOM异常. 很明显这个内存泄漏的症状.

 打开采样器Sampler, 对内存Memory进行采样, 发现byte[]数组占据了大量的内存空间, 这里是98%.

一般来说, 如果发现有大量的byte[]和char[], 首先想到的就是有没有new大量的字符串对象.值得一提的是,在JDK8及以前,String内部的数据是通过char[]来维护,但从JDK9以后,就改成了byte[].

通过进一步的观察发现, String并没有大量的实例,所以否定了new大量的字符串造成OOM的可能.

 我们点击Peform GC按钮, 手动触发一次Full GC, 发现使用的内存并没有减少, 可以确定是100%发生成内存泄漏.

点击 Heap Dump按钮生成堆转储文件进行GC ROOT分析.

发现一个现象, 这里byte[]实例数量只有4402个, 相比char[]5万多个实例来说并不多, 但是这4402个byte[]实例去占据了96.5%的内存空间, 所以可以判定, 现在的情况是:有少量的实例持有了大对象.

右键点击byte[], 选择Show in Instances View, 真相逐渐浮出水面...

按大小进行排序, 可以看到, 前面的byte[], 元素数量为99999999个, 而且通过下面的Arrays Items面板可以看到, 元素的值都是0, 直觉告诉我, 这肯定是一个buffer对象!

继续追踪GC ROOTS, 右键, Show Nearest GC Root:

可以看到, 果然是一个buffer, 并且被Http11Processor持有.

Http11Processor是何方神圣? 找到对应的源码, 在构造Http11Processor的时候, 传入了一个参数maxHttpHeaderSize, 值为99999999, 到这里, 真相已经大白了, 就是tomcat的参数server.maxHttpHeaderSize引发的泄漏问题.

 我们在SpringBoot的配置文件中配置了该值:

server.maxHttpHeaderSize=99999999

maxHttpHeaderSize默认大小为8K, 至于当初为什么要加这个参数, 有的接口接收一个以base64格式传过来的图像或PDF文件, 这时如果参数大小大于8K, 就会报HeadersTooLargeException.

Http11Processor的作用是读取 Channel 的数据来生成 ServletRequest 对象, 在Java NIO中, 读写是通过Buffer来实现的, 所以需要构造这个Buffer缓存区.

 解决方案

问题发现了, 那就要拿出解决方案来. 这个案例中, 是因为我们设置的maxHttpHeaderSize参数不合理导致的, 所以这个参数要根据业务方的需要, 设置一个合理的大小, 来限制最大的请求大小, 比如1M.

但是有时候业务就是上帝, 假如业务方要求至少要2M的大小, 也得满足, 这时候就要加钱加内存了. 这样确实能加大应用支持的并发数, 但也不能避免OOM, 毕竟单台机器内存不可能无限地加.

既然maxHttpHeaderSize参数一定得设置成这么大, 为了避免OOM, 只能从接受的请求量上面进行过滤了, 这时候可以通过参数

server.tomcat.max-threads=

设置最大并发的线程数, 并通过

server.tomcat.accept-count

参数设置一个合理的等待处理的连接数量.

线程数少了, 单个应用的并发量自然也就下降了, 要支持大的并发, 就要考虑横向堆节点机器了.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/344887.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号