工作时接手的一个项目线上 Full GC 次数过于频繁,最后 OOM 导致服务挂掉。
通过配置的 -XX:+HeapDumpOnOutOfMemoryError 拿到事故内存快照使用 MAT 进行分析。
首先可以看到 byte 数组占用了大量内存
Shallow Heap(浅堆)代表对象本身占用的内存,包含对象的对象头,实例数据、对齐空间。
Retained Heap(深堆)代表对象本身和对象关联的对象占用的内存,该对象被回收后,能够释放的内存大小。
exclude all phantom/weak/soft etc. reference 排除掉 软、弱、虚引用,只剩下强引用。因为除了强引用之外,其他引用都可以被 JVM GC 掉,如果一个对象始终无法被 GC,说明有强引用的存在,导致 GC 过程一直得不到回收,最终内存溢出、
我们排除掉软、弱、虚引用,因为这几种是不会造成内存泄漏的,可以先不用管它,我们只需要看排除后还有没引用存在,有的话 那就是强引用了
通过排查可以定位到 Http11InputBuffer
和 Http11OutputBuffer
两个缓冲对象都持有大数组。差不多每一个请求线程就占用 20 M,这显然是不合理的,应该是哪配置不规范导致的。
于是检查项目 yml 配置发现一条设置 max-http-header-size 属性与分配的内存相似。
server:
max-http-header-size: 1024000
该字段为设置 HTTP 消息头的最大大小。
其 Tomcat 默认设置的大小为 8 * 1024B 大小
org.apache.coyote.http11.Http11Processor#Http11Processor
可以看到输入输出缓冲对象初始化时携带上了设置了 maxHttpHeaderSize,进一步查看
只是初始化了下 Http11InputBuffer#headerBufferSize
属性值。
具体使用的地方为 org.apache.coyote.http11.Http11InputBuffer#init
方法
在定义缓冲区长度时,maxHttpHeaderSize(1024000) 额外添加了套接字通信的缓冲区大小(8192),所以在 GC 根链路上看到的 byteBuffer 所持数组大小为 10248192
allocate 方法会返回指定大小的 HeapByteBuffer 大小的缓冲区
outputBuffer 就在初始化的时候,就创建好了对应大小的缓冲区。此处就直接创建使用 maxHttpHeaderSize 大小。
对应上图 headerBuffer 所持数组 1024000 大小
Tomcat 接收请求时
根据该项目实际查看,请求传递参数不多,删除配置设置的请求头大小,使用默认参数即可
后续使用 jmeter 进行压测监控,内存基于稳定,不出现 OOM 问题。
看 Git 提交记录为 init 时就有了,怀疑是 copy 其他项目来着就一直用着了,为后续留下坑。
以上为对该 OOM 问题的一次排查过程记录,还是有很多知识点没有扫到,仍待继续学习。