【线上踩坑分享】server.max-http-header-size参数设置不当引发的OOM

742 阅读2分钟

server.max-http-header-size设置不当引发的线上OOM案例分析

问题现象

某天线上服务突然不可用,查看后台服务日志后发现有如下图所示异常:

异常关键字

java.lang.OutOfMemoryError: Java heap space

问题指向

o.a.c.h.Http11NioProtocol [DirectJDKLog.java:175] Failed to complete processing of a request

报错信息如下图所示:

image.png

问题定位

之后重启服务,运行一段时间后dump内存快照文件,并通过MAT工具进行分析,结果如下图所示:

异常信息:

有多个200MB的Http11OutputBuffer对象存在,这很不合理。

在这里插入图片描述

在这里插入图片描述

相关配置排查

排查相关配置后发现server.max-http-header-size被设置为200MB

在这里插入图片描述

问题分析

Tomcat在构建Http11OutputBuffer对象时会通过maxHttpHeaderSize来设置HTTP message header的大小,其默认为8KB,并通过ByteBuffer.allocate(headerBufferSize)向内存申请,也就是说每接收到一个HTTP请求,都会向内存申请200MB的空间,所以,当请求并发上来以后,必然会导致OOM。

源码分析

org.apache.coyote.http11.AbstractHttp11Protocol类,构建Processor对象

@Override
protected Processor createProcessor() {
    Http11Processor processor = new Http11Processor(this, adapter);
    return processor;
}

Http11Processor构建

public Http11Processor(AbstractHttp11Protocol<?> protocol, Adapter adapter) {
    super(adapter);
    this.protocol = protocol;
    httpParser = new HttpParser(protocol.getRelaxedPathChars(),
            protocol.getRelaxedQueryChars());
    inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpHeaderSize(),
            protocol.getRejectIllegalHeader(), httpParser);
    request.setInputBuffer(inputBuffer);
    // getMaxHttpHeaderSize默认为8KB,也可以通过server.max-http-header-size进行修改
    outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpHeaderSize());
    response.setOutputBuffer(outputBuffer);
    // Create and add the identity filters.
    inputBuffer.addFilter(new IdentityInputFilter(protocol.getMaxSwallowSize()));
    outputBuffer.addFilter(new IdentityOutputFilter());
    // Create and add the chunked filters.
    inputBuffer.addFilter(new ChunkedInputFilter(protocol.getMaxTrailerSize(),
            protocol.getAllowedTrailerHeadersInternal(), protocol.getMaxExtensionSize(),
            protocol.getMaxSwallowSize()));
    outputBuffer.addFilter(new ChunkedOutputFilter());
    // Create and add the void filters.
    inputBuffer.addFilter(new VoidInputFilter());
    outputBuffer.addFilter(new VoidOutputFilter());
    // Create and add buffered input filter
    inputBuffer.addFilter(new BufferedInputFilter());
    // Create and add the gzip filters.
    //inputBuffer.addFilter(new GzipInputFilter());
    outputBuffer.addFilter(new GzipOutputFilter());
    pluggableFilterIndex = inputBuffer.getFilters().length;
}
protected Http11OutputBuffer(Response response, int headerBufferSize) {
    this.response = response;
    // 内存申请
    headerBuffer = ByteBuffer.allocate(headerBufferSize);
    filterLibrary = new OutputFilter[0];
    activeFilters = new OutputFilter[0];
    lastActiveFilter = -1;
    responseFinished = false;
    outputStreamOutputBuffer = new SocketOutputBuffer();
}

解决方式

直接将server.max-http-header-size调整到合适的大小,且不要在header中放入大数据量信息。

延伸思考

OOM问题一般由两种原因导致:内存溢出以及内存泄漏

本文中发生的OOM就属于内存溢出导致的,解决这种问题最直接的方式就是增加内存,但通常我们在此之前我们还是要先初步判断一下,是滥用导致不够的,还是维持正常运营就需要这么多内存?当然,本文中的案例属于滥用导致的。

另一种导致OOM的原因就是内存泄露了,此类问题一般都是代码逻辑处理的有问题,申请的对象在使用完之后,由于某种原因,导致垃圾回收器不能回收,慢慢的积累下来最终导致内存会消耗殆尽,此时尝试加大内存可能会掩盖内存泄漏的问题,或者是延迟发生的时间,但是都没有彻底解决内存泄漏。

借助Eclipse MAT工具,可以帮助分析是否有存在内存泄露的可能。

image.png