webflux使用堆外内存(webflux threadlocal)

webflux使用堆外内存(webflux threadlocal)这段时间不太忙 记录一下前段时间遇到的一个问题 有一次经理给我说线上服务器收到报警 内存已经达到了 90 而且还有增长的可能 平时的内存基本上在 50 左右 一下子增加将近一倍的量 首先猜想可能是某个对象没有被释放掉或者说泄漏了 而且内存泄漏一般发生在堆内存的情况也比较多一点 下面介绍处理的过程 希望对大家有所帮助 如有错误或未考虑完全的地方 望不吝赐教 1 首先先在服务器中执行 top c

大家好,我是讯享网,很高兴认识大家。



这段时间不太忙,记录一下前段时间遇到的一个问题,有一次经理给我说线上服务器收到报警,内存已经达到了90%,而且还有增长的可能,平时的内存基本上在50%左右,一下子增加将近一倍的量,首先猜想可能是某个对象没有被释放掉或者说泄漏了,而且内存泄漏一般发生在堆内存的情况也比较多一点,下面介绍处理的过程,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教。

1、首先先在服务器中执行top -c 命令,配合Shift+P键,一般异常的几个进程会排到前几位

Spring Gateway堆外内存溢出_http
讯享网

可以看到PID为2817的这个进程占用的MEM,也就是memory内存数值比较高,由此锁定了这个2817进程

2、然后使用jstat命令,查看GC垃圾回收信息

上面的代码并不是当时的场景,当输入这句命令后,发现FGC、FGT执行的的次数和频率非常高,由此也进一步验证了内存泄露的猜想

3、使用jmap -heap PID命令也可以清楚的看到堆内存的信息

Spring Gateway堆外内存溢出_java_02

上图是平时堆内存的信息,因为时间有一段了,很多当时的情况没有截图记录,不过这个命令显示的信息还是很清晰的,当时发现老年代的used已使用容量已经达到了90以上,这也就导致堆内存一直在频繁的GC垃圾回收

4、接着还是使用命令继续推理 jmap -histo:live PID,可以显示出堆内存中的存活对象以及排名,有时候全部打印出控制台查看的不方便,也可以输出为文本jmap -histo:live PID > https://blog.51cto.com/u_/test.txt查看

Spring Gateway堆外内存溢出_java_03

当时发现存活对象中排名最高的前几位是char 和 byte类型,但是也无法清晰的看到具体引用的哪个对象或者说被哪个对象引用,所以开始借助别的工具查询

5、使用JProfile分析快照

当时启动jar包或者配置中也没有相应异常情况发生时的堆栈dump快照信息,感兴趣的小伙伴可以参考网上的其他资料,可以通过-XX:HeapDumpPath=xxx配置,本篇主要分享排查过程,不过当时的内存信息一直没有降下来,那就可以借助当时的快照也是可以的,使用

jmap -dump:format=b,file=xxx.hprof <pid>打印堆栈信息,有时候生成的文件还是挺大的,下载到本地,我使用的分析工具是Jprofile,其他的MAT或者jdk目录bin目录下的自带的jvisualvm.exe都是可以的

打开之后点击New Window

Spring Gateway堆外内存溢出_java_04

接着点击Open a snapshot打开快照

Spring Gateway堆外内存溢出_spring boot_05

接着点击Biggest Objects,查询内存中最大的对象

Spring Gateway堆外内存溢出_http_06

Spring Gateway堆外内存溢出_java_07

Spring Gateway堆外内存溢出_jvm_08

然后就发现内存中这两种对象的数量非常多,通过之前查看的项目日志也得知最近有过批量修改的操作,觉得那应该就是批量的问题了

那就开始排查byte对象中是被谁引用的

Spring Gateway堆外内存溢出_spring boot_09

Spring Gateway堆外内存溢出_tomcat_10

incoming references   表示这个对象被谁引用

outcoming references  表示这个对象引用的其他对象

所以在这里我选择的是incoming references,接着打开之后出现的对象就是只有我选择的byte对象的页面

Spring Gateway堆外内存溢出_java_11

点击查看GC Root路径,可以按照自己的需求选择对应的数量,我选择的是 All roots,显示的比较全面

Spring Gateway堆外内存溢出_jvm_12

Spring Gateway堆外内存溢出_jvm_13

然后发生了意想不到的一幕

Spring Gateway堆外内存溢出_jvm_14

咦,这不就正好是之前查看的Biggest Objects中的Http11OutputBuffer类似的对象吗?

后来通过排查资料得知,Http11OutputBuffer 和 Http11InputBuffer两个缓冲对象都持有大数组,而且在创建Http11InputBuffer对象时会使用项目中的maxHttpHeaderSize值设置,通过源码org.apache.coyote.http11.AbstractHttp11Protocol可以看到其默认的httpHeaderSize大小为8K,但是项目中配置的大小为 

server.max-http-header-size=

所以每一个HTTP请求,都会向内存申请大小的的空间,每次生成的新生的对象会先存入新生代,那么在并发请求的情况下,对于这种内存大的对象,新生代很快就没有足够的内存去分配,再后面的像这样的对象都会直接存在老年代,当老年代内存不足就会引起full GC,这些对象都无法被回收,通过之前的jstat命令行也可以看出GC的情况,在频繁Full GC(jvm在进行濒死挣扎)后,内存还是不足,那么最后就会报出内存溢出,再严重就会出现宕机的可能

查看项目中这个配置也是一开始加进来的,而且一般默认的配置就足够了,于是就把server.max-http-header-size这行配置给注释掉,使用默认值8k。上线后再次尝试发现不会再出现内存溢出报警的问题了。

小讯
上一篇 2025-05-10 09:31
下一篇 2025-05-06 15:00

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/168962.html