一次线上JVM内存溢出分析,GC分析、MAT、gcviewer

本文详细记录了一次由于maxHttpHeaderSize配置不当导致的线上JVM内存溢出问题的分析过程。通过MAT工具和GCViewer,发现大量Http11OutputBuffer实例消耗内存,根源在于Tomcat的NIO线程缓冲区设置。优化配置后,解决了内存占用过高的问题,加深了对Tomcat工作原理的理解。
摘要由CSDN通过智能技术生成

maxHttpHeaderSize带来的问题

前因

我们的服务是部署在docker容器中,使用SpringBoot框架搭建的微服务,jdk版本是open jdk 1.8_u201版,内存分配了4G,共部署了4个微服务,使用gateway作为网关负载均衡。有一天运营团队通知我,我们的服务不能访问,访问的页面都是没有数据的;我随即查看我们的系统运行状况,我是通过docker exec -it <容器ID> jstat -gcutil <pid> 1000来查看的;发现系统的年轻代占用50%,老年代占用97%,元空间占用93%,查看GC次数发现没有触发FullGC,只是触发了YGC 68次。

这下让我产生疑问,为什么内存几乎被占满了,jvm还不进行FullGC呢?

于是我通过命令docker exec -it <容器ID> jmap -dump:file=<filename> <pid>来生成dump快照文件,还有获取项目中的gc日志。而且通过MAT分析dump、使用gcviewer分析GC日志。

MAT分析工具 https://www.eclipse.org/mat/downloads.php
gcviewer https://github.com/chewiebug/GCViewer/releases

先来看看GC日志情况

java -jar ./gcviewer.jar service_gc.log

这里可以看到总的GC暂停次数和时间,和FullGC暂停次数和时间

项目刚刚启动时,GC情况,jvm堆内存逐步变大,黄色代表年轻代,紫色代表老年代。

到最后的阶段

我们再来看看MAT分析情况是怎样:

出现两个可能发生内存溢出的问题

  1. byte[]占用堆内存比例约为46.24%
  2. 有61个实例Http11OutputBuffer被系统加载,总共耗费内存46.16%

这里的指向的问题的线程是org.apache.tomcat.util.threads.TaskThread

继续往下看,发现跟我们项目有点接近的东西tk.mybatis.mapper.mapperhelper.EntityHelper,这个是实体类工具类 - 处理实体和数据库表以及字段关键的一个类。我们使用了这个插件,相信大家用过mybatis都会知道MyBatisPlus,其实tk.mapper做的功能也是和MyBatisPlus差不多。

那么这里为啥会装那么多的tk.mapper对象呢,主要来源是查数据库后转换实体类而创建的,我们再看看他的GC Roots最近节点

发现都是在org.apache.tomcat.util.threads.TaskThread类引用,我们打开这个类看看源码。

终于找到了与tomcat相关的类了,开心!!!!!

public class Nio2Endpoint extends AbstractJsseEndpoint<Nio2Channel,AsynchronousSocketChannel> {
   
	...
     @Override
    public void bind() throws Exception {
   

        // Create worker collection
        if (getExecutor() == null) {
   
            createExecutor();  // 统一在这个方法创建线程池
        }
        if (getExecutor() instanceof ExecutorService) {
   
            threadGroup = AsynchronousChannelGroup.withThreadPool((ExecutorService) getExecutor());
        }
        // AsynchronousChannelGroup needs exclusive access to its executor service
        if (!internalExecutor) {
   
            log.warn(sm.getString("endpoint.nio2.exclusiveExecutor"));
        }

        serverSock = AsynchronousServerSocketChannel.open(threadGroup);
        socketProperties.setProperties(serverSock);
        InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
        serverSock.bind(addr, getAcceptCount());

        /
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值