当前位置 博文首页 > 少年时未觉悟 ,觉悟时不再年少,还危机四伏!:Tomcat 多模块部
产品新一轮压力测试,这几天不知跑了什么任务,后台测试环境 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