问题

线上程序出现了OOM,程序日志中的输出为

Exception in thread "http-nio-8080-exec-1027" java.lang.OutOfMemoryError: Java heap space
Exception in thread "http-nio-8080-exec-1031" java.lang.OutOfMemoryError: Java heap space

看线程名称应该是tomcat的nio工作线程,线程在处理程序的时候因为无法在堆中分配更多内存出现了OOM,幸好JVM启动参数配置了-XX: HeapDumpOnOutOfMemoryError,使用MAT打开拿到的hprof文件进行分析。

第一步就是打开Histogram看看占用内存最大的是什么对象:

可以看到byte数组占用了接近JVM配置的最大堆的大小也就是8GB,显然这是OOM的原因。

第二步看一下究竟是哪些byte数组,数组是啥内容:

可以看到很明显这和HTTP请求相关,一个数组大概是10M的大小。

第三步通过查看GC根查看谁持有了数组的引用:

这符合之前的猜测,是tomcat的线程在处理过程中分配了10M的buffer在堆上。

至此,马上可以想到一定是什么参数设置的不合理导致了这种情况,一般而言tomcat不可能为每一个请求分配如此大的buffer。

第四步就是检查代码里是否有tomcat或服务器相关配置,看到有这么一个配置:

max-http-header-size: 10000000

至此,基本已经确定了八九不离十就是这个不合理的最大http请求头参数导致的问题。

关于http header最大长度的那些事

http协议,超文本传输协议,HyperText Transfer Protocol,是互联网上应用最为广泛的一种网络协议,所有的WWW文件都遵守这个标准。

关于http协议消息的格式,大家可以网上自行搜索,这里不再赘述。

本文关注的是其header部分,如下图所示(红框标注部分):

问题原型

有一个web application提供web service,这个web application基于java开发,部署在tomcat容器上。

问题是:当客户端发送一个GET请求,结果得到400的response,意思是说bad request。

检查了这个request的代码实现逻辑,并没有相关input validation的逻辑,并且检查server端日志发现,request请求似乎并没有到达我们自己代码实现逻辑部分。这是为什么呢?

问题解释

遇到这个问题时,第一步就是查看server端日志,但是觉得很tricky的是,最开始并没有发现相关的日志,只是发现request并没有到达我们自己代码实现逻辑部分。

后来,mina同学眼神很好,发现了如下日志:

通过日志note信息发现,该条日志在info级别下只会打印一次,之后都会是debug级别才打印,难怪之前没有注意到这条日志。

从日志信息可知,request的header部分太大,超过了tomcat允许的最大值。

默认情况下,tomcat(8.0版本)允许的http请求header的最大值是8024个字节(8KB)。

那为什么之前没有出现这个问题呢?

原因是,项目迁移到SCP平台上之后,改成JWT token做权限校验,这个JWT token会被添加到request的header,然而JWT token一般来说都很大(平均有6k个字节左右),所以说在增加了JWT token这个header以及其他一些相关的headers之后,整个request的header部分就超过8024个字节,于是就出现了这个问题。

那么如何解决这个问题呢?可以从两个方面考虑:

增加tomcat允许http header最大值。这个配置参数maxHttpHeaderSize可以设置tomcat允许的http header最大值。

减少header的size,比如不要添加无关的header到request。

扩展

在研究这个问题的过程中,其实还有一些其他疑问。首先,一个request的转发流程大致如下:

那么,在这个流程中,为什么request在前面的部分没有出现这个问题,而这个问题出现在最后一个技术栈是java/tomcat的component呢?

原因是,每个web服务器的http header最大长度的默认值不一样,同时随语言、版本不同也会不一样。举个例子tomcat 5的http header size的默认值是4K。

我找到了其他component中对于http header size的默认值的定义:

CF Router是用Go语言实现,Go语言的http处理模块对于它的定义是默认值1MB。

App Router是用Nodejs实现,Nodejs的http处理模块对它的定义是默认值80KB。

以上两个默认值都要远远大于8KB,这也就解释了没什么问题出在最后一个component。

Tomcat修改maxParameterCount配置

问题

java.lang.IllegalStateException: More than the maximum number of request
 parameters (GET plus POST) for a single request ([10,000]) were detected.
  Any parameters beyond this limit have been ignored.
   To change this limit, set the maxParameterCount attribute
    on the Connector.

解决方案

以前使用外部Tomcat部署项目的时候,可以通过修改server.xml文件中的Connector节点maxParameterCount属性值解决这个问题。

<Connector port=“8080” redirectPort=“8443” protocol=“HTTP/1.1” maxParameterCount="-1" />

因为SpringBoot使用的是内嵌的Tomcat,无法配置server.xml。经过查看相关API文档并没有发现可以直接在配置文件中配置maxParameterCount属性,那么我们就在代码中进行配置,在SpringBoot的API文档中讲解了通过实现WebServerFactoryCustomizer接口可以对Tomcat进行相关配置。

参考

自定义tomcat配置

创建一个类并实现WebServerFactoryCustomizer接口的customize方法。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

/**
 * 自定义Tomcat容器配置类
 *
 */
@Component
public class MyTomcatWebServerFactoryCustomizer 
        implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {

    public static final int DEFAULT_MAX_PARAMETER_COUNT = 10000;

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 单次请求参数最大限制数
     */
    @Value("${server.tomcat.maxParameterCount}")
    private int maxParameterCount = DEFAULT_MAX_PARAMETER_COUNT;

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        if (logger.isDebugEnabled()) {
            logger.debug("MyTomcatWebServerFactoryCustomizer customize");
        }

        PropertyMapper propertyMapper = PropertyMapper.get();

        propertyMapper.from(this::getMaxParameterCount)
                .when((maxParameterCount) -> maxParameterCount != DEFAULT_MAX_PARAMETER_COUNT)
                .to((maxParameterCount) -> customizerMaxParameterCount(factory, maxParameterCount));
    }

    /**
     * 配置内置Tomcat单次请求参数限制
     *
     * @param factory
     * @param maxParameterCount
     */
    private void customizerMaxParameterCount(TomcatServletWebServerFactory factory, 
                                             int maxParameterCount) {
        factory.addConnectorCustomizers(
                connector -> connector.setMaxParameterCount(maxParameterCount));
    }

    public void setMaxParameterCount(int maxParameterCount) {
        this.maxParameterCount = maxParameterCount;
    }

    public int getMaxParameterCount() {
        return maxParameterCount;
    }
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持Devmax。

springboot max-http-header-size最大长度的那些事及JVM调优方式的更多相关文章

  1. SpringBoot本地磁盘映射问题

    这篇文章主要介绍了SpringBoot本地磁盘映射问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  2. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  3. SpringBoot整合Javamail实现邮件发送的详细过程

    日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等,下面这篇文章主要给大家介绍了关于SpringBoot整合Javamail实现邮件发送的详细过程,需要的朋友可以参考下

  4. SpringBoot详细讲解视图整合引擎thymeleaf

    这篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,类似于Velocity、FreeMarker等传统引擎,关于其更多相关内容,需要的小伙伴可以参考一下

  5. Springboot集成mybatis实现多数据源配置详解流程

    在日常开发中,若遇到多个数据源的需求,怎么办呢?通过springboot集成mybatis实现多数据源配置,简单尝试一下,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. SpringBoot使用Minio进行文件存储的实现

    本文主要介绍了SpringBoot使用Minio进行文件存储的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. 解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

    这篇文章主要介绍了SpringBoot中使用LoadTimeWeaving技术实现AOP功能,AOP面向切面编程,通过为目标类织入切面的方式,实现对目标类功能的增强,本文给大家介绍的非常详细,需要的朋友可以参考下

  8. 详解springboot测试类注解

    这篇文章主要介绍了springboot测试类注解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  9. SpringBoot实现过滤器和拦截器的方法

    大家应该都晓得实现过滤器需要实现 javax.servlet.Filter 接口,而拦截器会在处理指定请求之前和之后进行相关操作,配置拦截器需要两步,本文通过实例代码给大家介绍SpringBoot 过滤器和拦截器的相关知识,感兴趣的朋友一起看看吧

  10. SpringBoot中使用@scheduled定时执行任务的坑

    本文主要介绍了SpringBoot中使用@scheduled定时执行任务的坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部