当前位置 博文首页 > 少年时未觉悟 ,觉悟时不再年少,还危机四伏!:Tomcat 多模块部

    少年时未觉悟 ,觉悟时不再年少,还危机四伏!:Tomcat 多模块部

    作者:[db:作者] 时间:2021-07-16 15:32

    背景

    产品新一轮压力测试,这几天不知跑了什么任务,后台测试环境 CPU 超高导致服务不可用,记录一下跟踪的过程。

    定位进程

    top 查看进程 CPU 超高的是 Tomcat 进程:
    在这里插入图片描述
    查看对应线程
    在这里插入图片描述
    打印线程编号:

    printf  "%x" 7517
    jstack 7492 | grep '1d5d'
    

    堆栈信息显示这个线程是 GC 线程,而且全部的 GC 线程都在工作,是典型的 GC 导致的 “Stop the world” 后遗症。
    在这里插入图片描述

    定位问题模块

    查看日志,容器日志中的内存溢出异常:

    Exception: java.lang.OutOfMemoryError thrown from the 
    UncaughtExceptionHandler in thread "quartzScheduler_QuartzSchedulerThread"
    

    由于 Tomcat 的 webapps 目录下有多个模块,而容器日志的异常并不全,无法定位具体的模块。可以确定的是凌晨 CPU 超高时执行 Quartz 定时任务的有两个模块,猜测是距离内存溢出最近的应用,就先移除另一个应用,继续监控。

    监控一阵儿后,测试部需要继续测试,就把另一个模块先还原回去了,并顺带监控了下它的日志。Quartz 触发了上次未执行的定时任务

    [org.springframework.scheduling.quartz.LocalDataSourceJobStore] -
     localhost-startStop-2 - 
     Handling 2 trigger(s) that missed their scheduled fire-time
    

    后面接着就是报错的任务日志:

    ERROR [org.quartz.core.JobRunShell] - DefaultQuartzScheduler_Worker-1 - xx.xx 
    threw an unhandled Exception: 
    java.lang.OutOfMemoryError: GC overhead limit exceeded
    	at java.util.Arrays.copyOf(Arrays.java:3181)
    	at java.util.ArrayList.grow(ArrayList.java:261)
    	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
    	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
    	at java.util.ArrayList.add(ArrayList.java:458)
    	at XX.xx(XXX.java:xx)
    

    这次打印的异常比较完整,可以定位到具体的错误,根源是代码缺陷,一次从 ES 中查询了过多的数据到内存,导致内存溢出。

    之前已经调整过 JVM 参数,最大堆内存为 3G ,再优化参数,添加 OOM 导出配置:

    -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/xx.dump
    

    启示录

    OOM 时 dump 日志文件也是够大的,超过 3G,这破网也没下载成功过。

    用 jmap 打印堆数据,占据内存最大的是 char[] 对象,工程中有大量的字符串拼接操作,但压死内存的最后一根稻草是大对象,但是优化字符串拼接也是必须的,毕竟是一个包含多模块的大工程。

    多模块开发,而且每个模块都用了 SpringBoot ,还是有点坑的。除了部署时重复引用的 jar 包导致的磁盘资源浪费外,还有 JVM 内存资源的消耗,每个应用的类加载器都要加载自己的 jar 包,这个 OOM 就是潜在的威胁!

    cs