maxPostSize导致的内存溢出以及配置需要注意的问题

32 篇文章 0 订阅

拓展:踩坑记录-maxHttpHeaderSize配置-内存都去哪儿

关于Tomcat的maxPostSize属性的配置需要注意的问题

需求:
近期要做大文件上传功能,除了修改前端框架上传控件jQuery Uploadify的上传文件限制大小和Spring MVC框架配置的文件上传模块中的MultipartResolver中的文件上传限制大小,还需要去修改Nginx服务器中conf目录下的nginx.conf配置文件中的client_max_body_size属性以及Tomcat服务器中conf目录下的server.xml配置文件中的connectionTimeout属性、maxPostSize属性,以上内容就是我做的修改。

问题:
先是修改Nginx服务器和Tomcat服务器的配置,重启Nginx服务器,再是修改页面和代码后重新部署和运行,然后在页面进行大文件上传操作,然后提交保存都出现了问题,自己怀疑可能是大文件保存出现了问题,忙着去查看服务器上的日志,接着同事又说文件删除功能失效,接下来又有同事说网站登录不上了,输入正确的验证码后报错说是验证码登录过期,,,,各种问题接踵而至,看了半天的服务器日志没有发现问题,还是自己看的不仔细,也没有定位到问题出现在哪里。

排查:
无奈去请求大牛,先是问我修改了哪些东西,我说Nginx服务器和Tomcat服务器的配置我都修改了,大牛看了Linux上的Nginx服务器的配置文件和Tomcat服务器的server.xml配置文件,回头问我maxPostSize=“0”属性的配置是什么意思,我说是HTTP的POST请求下的body大小不做限制,如果不设置的话,缺省的情况下是maxPostSize=“2097152”,也就是2M的大小,因为单位是Byte。大牛接着去查看了相关资料,得知可能是Tomcat版本带来的问题,不同的Tomcat版本maxPostSize属性设置的可能有差异,知道了问题所在,那么就去着手进行配置了。

解决:
百度了下Tomcat maxPostSize这两个关键词,很幸运的是第一条就是关于Tomcat不同版本的maxPostSize的设置,

这篇博客内容

看了这篇博客,接着去查看Linux下的项目使用的统一的Tomcat版本,使用的版本是Tomcat 7.0.70,接着看这篇文章你还能说什么,改呗,将属性maxPostSize修改为-1,代表不限制,重启Tomcat服务器,重新登录网站和以上出现的问题都没有了。

总结:
想着昨天出现的这个问题,今天休息正好可以总结一下,接着去看Tomcat官网上的相关文档,去看下这个问题。
如今Tomcat版本更新很快,很多Tomcat7的版本不再好找,我们可以查看最新的Tomcat7版本信息,打开Tomcat官网后,查看Documention下的Tomcat7.0

接着查看Reference下的Configuration部分

接着查看Connectors下的HTTP部分,这个和Tomcat下的conf中的server.xml配置文件的结构有很大的相关性,只要你很熟悉server.xml配置,那么找到这个不是问题

查看相关属性, 我们就会看到maxPostSize的详细介绍,这个限制必须设置为小于0的负数才能不限制,缺省下是 2097152,也就是2M的大小,单位是Byte。

你要知道以上的介绍仅仅是Tomcat 7.0.86版本的信息

那么我们怎么看Tomcat历史版本的变化呢,回到刚开始进来的文档首页,点击最下方的Changelog部分,

这里面是Tomcat7所有的历史版本的变化,我们找到Tomcat 7.0.63版本,可以看到变化的第一项就是关于maxPost属性的内容,上面的博客的哥们果然是满满的干货,虽然内容少,但是一击必中啊。

通过以上的总结,如果以后出现类似的问题,排查问题的能力要有所提升,自己总结问题的排查步骤:
第一:查看服务器上的各种日志
第二:查看服务器上的各种配置文件
第三:考虑各个版本的差异,就比如上面的问题,这个前提是你要知道你修改了哪些配置,增加了哪些配置。



参考链接:http://www.java8.com/thread-28319-1-1.html

踩坑记录-maxHttpHeaderSize配置-内存都去哪儿

遇到一个问题,自己部署的线上springboot服务总是内存飙高,尤其是在30个以上的请求并发的时候,内存会立马大涨,并出现报错:
java.lang.OutOfMemoryError: Java heap space
后来通过一步一步排查发现了导致这个问题的原因,就是当初在设置springboot的上传文件大小限制的时候加了一个配置:
server.maxHttpHeaderSize=102400000意思是http最大请求头大小设置成了大约100M,springboot默认的maxHttpHeaderSize是8K,如果没有特殊需要,应该是够用的。把这个配置注释掉就可以解决掉我提到的这个问题。
遇到跟我一样问题的小伙伴,可以检查一下是不是跟我犯了一样的错误,到这儿这个文章也可以结束了,但是鉴于这一次问题的排查过程走了很多“弯路”,不过却学到了很多东西,所以把我找原因,并且找到原因之后定位导致这个错误的具体是哪一行代码的过程记录一下,有兴趣的小伙伴可以看一下。
会涉及到哪些知识点?

接下来要讲的会涉及到一些知识点,不懂也没关系,我会简单说明:
JVM内存结构基础理解:

jvm内存分成三块:堆内存(Heap Space)、方法区(Method Area/PermGen)和栈(Native Area)。

堆内存:是内存大头,一般程序出现内存问题都在这里。由新生代和老生代组成。新生代由Eden空间、From Survivor空间、To Survivor空间组成。
方法区:存储类信息、常量、静态变量等数据。又叫Non-Heap
栈:主要用于方法的执行
JVM两种GC机制:

GC是指java自动回收内存的机制,有两种方式:

  • Minor GC:发生在新生代的垃圾收集动作。比较频繁,回收速度快。
  • Major GC/Full GC 是老年代GC(通常伴随Minor GC),速度很慢。

因此要尽量避免触发Full GC.
JVM内存分配机制:

简单介绍一下java中新建的对象,一般会放在新生代的Eden,经过Minor gc之后没有被销毁的对象会被复制到新生代的Survivor区域(至于为什么Survivor分为from to,这里涉及到一个复制算法,有兴趣的可以自行查阅)
对象会被分配到老生代的几种情况:

  • 经历了默认15次minor gc之后,还没被销毁
  • 动态对象年龄判断
  • 大对象直接进入老年代(这一次错误的主因)
  • MinorGC后的对象太多无法放入Survivor区
  • 老年代空间分配担保规则

JVM命令:

假设我们的java进程号是11111
//jstack命令用来输出指定进程当前所有线程的运行步骤,用来查是不是又死循环很有用,结果输出到jstack.log文件jstack 88059 > jstack.log//jmap -histo 用来打印当前java进程有哪些对象,这些对象的数量和占用的内存大小分别是多少,结果输出到jmap.log文件jmap -histo 11111 > jmap.logjconsole

jconsole是一个java自带的查看进程内存情况的GUI神器,具体可以搜一下“springboot jconsole”有很多教程。简单看一下这个连接界面和内存监控界面,非常好用,也可以通过这个自己做一些小实验,对JVM内存机制进一步学习。

jmeter:压力测试工具

为什么会有这个配置?

先说一下为什么我会添加这个配置。有一个需求,是要通过base64上传大文件,springboot默认是由文件上传大小限制的,通过网上查,找到添加如下配置:
spring.http.multipart.max-file-size=20Mb spring.http.multipart.max-request-size=60Mb 但是这个配置只是针对multipart形式上传文件的限制有效,base64形式的这个配置无效,上传文件大小还是限制得很小,又从网上找到了配置:
server.maxHttpHeaderSize=102400000server.maxHttpPostSize =102400000设置完之后问题得到解决
为啥服务老挂?

在修改完配置的很长一段时间内,使用一直没什么问题,但是随着用户量增多,并且提交了一些爬虫(百度,360,搜狗)的申请,外加添加了网站的sitemap之后,网站访问频率变高,服务时不时出现报错
java.lang.OutOfMemoryError: Java heap space检查nginx日志之后发现,尤其是在一秒钟有超过20个请求的情况下,就经常出现这个错误,一旦出现这个错误,服务的内存就很高,一个空的接口返回时间,也要几十秒,十分不正常。
然后就开始了长时间挂了就重启,甚至封锁了有些搜索引擎的爬虫,来降低网站的被访问频率,降低造成OOM带来的用户影响。还对经常被爬虫访问的接口做了两三天的接口优化,每一次把优化上线,都祈祷着再也不要出现这个报错,但是事与愿违。
哪里的内存出了问题:

这个问题在线上的时候,是偶发的,因为当时没有想到是并发造成的,觉得可能是业务中某个参数异常造成的。每一次出现这个问题:就利用jstack命令,查看了每一个线程运行的步骤,几次查找下来,都没有结果。
再利用jmap -histo 查明每一个对象的数量以及它占用的内存,发现了异常情况下,内存的增长量几乎都是集中在“[B”,也就是字节数组,字节数组的话就很难定位具体对象,如果是我们业务中的某个自己写的对象的问,问题马上就能找到。
利用jconsole,查看每一次内存被占满都是在老生代区域,手动执行jconsole上的gc之后,内存都还是这么高。但是每一次重启完服务之后内存都很低,看着很舒服,重启大法屡试不爽,但是终究解决不了问题。
会不会不是自己业务接口的原因?

综合分析一下:

根据出问题时间的nginx日志访问统计,得出可能性结论,并发会导致这个问题出现,在测试环境用jmeter测试之后发现果然,并发就会出现OOM报错。
内存问题出现在老生代,并且在并发之后,老生代内存一下飙高,结合对象进入老生代的几种情况,猜测是不是创建了什么超级大的对象,第一反应是不是读取了什么大文件?查找代码之后无果。
几次调优之后不见效果,不禁怀疑,是不是跟业务代码无关,于是乎把自己的服务本地启动起来,针对出问题的接口,利用jmeter发起30QPS的查询,持续三十秒。
不出意料,报OOM错误,确认这个情况本地能复现且必现。
再编写了一个空业务的接口,controller层直接返回,做同样的压测,竟然也出现了OOM。这说明跟自己的业务没半毛钱关系,也就是之前的查询业务优化确实起不到任何作用。
排除了业务上的问题的可能性,再下一步怀疑是不是引入的某个包导致的,于是一层一层的去掉如引入的包,去掉redis,mybatis,okhttp等等 ,甚至去掉了springcloud依赖只剩下springboot,依然会出现OOM。
这样排除下来,基本上就只剩下一种可能,就是配置文件的问题,于是通过配置文件中的配置一个个删除,终于定位到了最开始提到的那一行maxHttpHeaderSize的配置。
这个配置在哪里生效?为什么会导致这个问题?

确定了是配置的问题之后,总算松了一口气,线上服务有救了,但是为什么这么一个配置,会导致内存溢出呢,他不就是规定http请求头最大限制,超过这个限制就应该报错而已嘛?那我们就追踪源代码一探究竟。
首先这个配置会在哪里被读取呢?我按照做framework的人的老思路,配置一般都会被装配成配置实体类,既然他是"server."开头的,那是不是会有一个ServerConfig,或者ServerProperties这样的类。

利用代码提示,果真找到了有这么一个类。在这个类中也确实找到了这么一个变量,跟这个配置的命名是一致的。so lucky!

可以看到这个配置默认大小是8KB。当然这个配置在哪个类里面在百度应该也能搜到具体在哪。
再下一步,找到这个变量的get方法,查找这个方法的所有调用可以看到有以下四个地方调用了这个get方法:

我们知道springboot是内置服务容器的,而默认内置的就是tomcat容器,因此我们双击查看第四条,也就是我框出来的那一条。

再对这个方法查找调用;

很好,只有这一个调用。双击打开之后如下:

这里的PropertyMapper看一下它的注释,大概意思是从一个地方拷贝值到另一个地方,从from拷贝到to,那我们进入第90行的customizeMaxHttpHeaderSize方法

这里看到maxHttpHeaderSize被赋值给了AbstractHttp11Protocol的一个对象,那么进入类,找到maxHttpHeaderSize变量

在这个类里面搜了一下maxHttpHeaderSize,很幸运,这个变量只有get,set这两个方法在用,内部其他地方没有用到,大大降低了我们跟踪的复杂度,同样,对这里的get方法做查找调用

只有一个地方调用,跟踪到Http11Processor里面调用getMaxHttpHeaderSize方法的代码8 };

这里一个是Http11InputBuffer相关的,一个是Http11OutputBuffer相关的,先来看Http11InputBuffer

被赋值给了headerBufferSize,然后我们看一下Http11InputBuffer中有使用到headerBufferSize的几个地方

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

゛Smlie。

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值