Tomcat 线上调优记录

文章讨论了原始Tomcat配置导致的线程飙升和内存溢出问题,表现为数据库连接不可用和大量OOM。第一次修改减少了Tomcat的最大线程数,优化了堆内存配置,并引入了本地缓存。第二次修改通过调整JVM堆内存和使用CMSGC算法来缓解CPU和内存压力。第三次修改进一步优化了GC算法,以应对高并发下的内存管理。文章强调了线程配置、JVM内存设置和GC算法选择的重要性,并推荐了监控工具如PinPoint和VisualVM。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原始Tomcat配置

  • 启动参数

Plaintext
-Xms256m -Xmx512m -XX:MaxPermSize=128m

  • Tomcat 参数配置

XML
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
 maxThreads="1500" minSpareThreads="50" maxIdleTime="600000"/>
    <Connector
               port="8801" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
               connectionTimeout="20000" URIEncoding="UTF-8"
               enableLookups="false"
               sendReasonPhrase="true"
               useBodyEncodingForURI="true"  maxThreads="500"
               compression="on" compressionMinSize="2048"
               compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
               acceptCount="1200" disableUploadTimeout="true"/>

第一次修改

问题表现

  • Tomcat 接口重启,tomcat 线程飙升到1000+,短时间内出现大量OOM,表现出的现象数据库连接不可用,数据查询超时等。进而导致服务不可用

Plaintext
?[m?[1;35m2022-04-13 19:01:42 ERROR [http-nio2-8803-exec-500] (ResultInterceptor.java:172) [result exception...msgType : 3, code : 99, msg : Could not get JDBC Connection; nested exception is com.alibaba.druid.pool.GetConnectionTimeoutException: wait millis 10656, active 18, maxActive 24, creating 0, error: com.alibaba.druid.pool.DruidDataSource.getConnectionInternal(DruidDataSource.java:1775)       
com.alibaba.druid.pool.DruidDataSource.getConnectionDirect(DruidDataSource.java:1427)       
com.alibaba.druid.filter.FilterChainImpl.dataSource_connect(FilterChainImpl.java:5059) ]

Plaintext
?[m?[1;35m2022-04-13 19:05:26 ERROR [http-nio2-8812-exec-303] (ResultInterceptor.java:172) [result
?[mognl.MethodFailedException: Method "handleMsg" failed for object friendGame.action.HandleMsgAction@31aa0e68 [java.lang.OutOfMemoryError: GC overhead limit exceeded]
        at ognl.OgnlRuntime.callAppropriateMethod(OgnlRuntime.java:1932)
        at ognl.ObjectMethodAccessor.callMethod(ObjectMethodAccessor.java:68)
        at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethodWithDebugInfo(XWorkMethodAccessor.java:98)
        at com.opensymphony.xwork2.ognl.accessor.XWorkMethodAccessor.callMethod(XWorkMethodAccessor.java:90)
        at ognl.OgnlRuntime.callMethod(OgnlRuntime.java:1996)

  • 短暂的出现大量memcached 的连接

推测问题原因

  • Tomcat 线程数配置的过高,减少tomcat的最大线程配置,调大tomcat 的初始线程数
  • 服务中过度依赖memcached ,一个登录操作可能存在几十个查询操作,memcached响应过慢时,会导致tomcat 导致创建大量线程
  • 基于 xmemcached 作为memcached连接SDK 其底层消息响应机制基于java.util.concurrent.CountDownLatch 实现,过多使用会导致cpu 使用率增高

程序修改点

  • 启动参数

堆最大小统一为512MB ,移除jdk7的永久代参数配置,新增新生代配置

Plaintext
 -Xms512m -Xmx512m -Xmn256m

Tomcat  参数配置

  • 移除Executor 线程池配置减少冗余线程
  • Connector maxThreads修改为500,新增  minSpareThreads="50" 初始化线程为50

Plaintext
<!--    <Executor name="tomcatThreadPool" namePrefix="catalina-exec-"-->
<!--        maxThreads="1500" minSpareThreads="50" maxIdleTime="600000"/>-->

    <Connector
               port="8801" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
               connectionTimeout="20000" URIEncoding="UTF-8"
               enableLookups="false"
               sendReasonPhrase="true"
               useBodyEncodingForURI="true"
minSpareThreads="50" maxThreads="500"
               compression="on" compressionMinSize="2048"
               compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
               acceptCount="1200" disableUploadTimeout="true"/>

优化点

  • 限制tomcat 的线程数,避免重启时(瞬时高峰)导致线程创建过多
  • 修改堆的最大最小值,降低堆内存的动态变化的性能影响
  • 项目中对应公共不变的数据,采用本地缓存(Caffeine )
  • 接口服务对接PinPoint 进行监控

仍存在的问题

  • 重启时Tomcat 仍创建到了 线程池限制的最大值
  • 短时间出现memcached 连接超时大量减少,但极少连接超时信息
  • 重启时客户端能看到短时间的卡顿

第二次修改

问题表现

  • 项目下午三点更新,晚七点高峰期开始收到服务器资源预警,CPU和内存使用暴增,观察接口出现大量数据连接异常和OOM 日志
  • 观察PinPoint 上日志记录 更新后tomcat 线程持续保持在最大值附近,jvm频繁的出现full gc
  • 查看历史的升级记录,每次升级tomcat 线程都是保持到最大值持续到第二天凌晨开始降低

推测问题原因

  • 对比其它项目组的jvm内存配置,jvm 堆内存设置偏小
  • 运维的通过监听tomcat端口号的限流策略存在不合理处。在端口号启动后将有半分钟左右的服务不可用空窗期,大量的消息堆积导致至服务可用时,tomcat 的处理线程被压满,因为tomcat 的线程销毁策略是60s内没有被使用。故tomcat 的线程数目在凌晨左右开始降低

程序修改点

  • 启动参数
  • 提升JVM 的堆内存为1g
  • 设置元空间的初始大小与最大值
  • 设置JVM 发生OOM 时保存堆信息到指定文件

Plaintext
-Xms1g
-Xmx1g
-Xmn512m
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=320m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/gcweb/jdk_dump

优化点

  • 扩大堆的内存限制,增大服务器资源的使用
  • 设置新生代的的初始值,避免在未达到初始值前提下每次扩增时带来的FullGC
  • 记录OOM的堆信息况
  • 运维人员使用新的重启放流策略,采用监控url 方式取代端口监听。 重启时限制nginx 发送消息到接口,监控接口服务检测url 地址 是否可用,第一次可用时延期1min 再次检测是否可用。两次可用时,nginx 开始放流
  • 修改接口重启时各项指标保持在正常水平,客户端没有明显的卡顿感

第三次修改

问题表现

  • pinpoint上观察堆使用明显偏高,full gc 的一天有三四次,在12,19 点高峰期尤为明显

推测问题原因

  • 本地保持与线上相同的tomcat配置,压测接口。并通过visualvm 观察jvm 堆信息。
  • 压测后发现 s1,s0 逐渐变小,eden 开始变大,导致 eden  gc时 s0,s1容量无法承受eden  的对象信息,导致大量对象进入老年代,进而fullgc 比较频繁

程序修改点

  • 启动参数
  • 指定CMS GC 算法

Plaintext
-Xms1g
-Xmx1g
-Xmn512m
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=320m
-XX:+UseConcMarkSweepGC
-XX:+UseCMSCompactAtFullCollection
-XX:CMSInitiatingOccupancyFraction=70
-XX:+CMSParallelRemarkEnabled
-XX:SoftRefLRUPolicyMSPerMB=0
-XX:SurvivorRatio=8
-XX:-OmitStackTraceInFastThrow
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/home/gcweb/

优化点

  • 指定CMS 作为 老年代的GC 算法,并开启GC 后压缩UseCMSCompactAtFullCollection
  • 通过指定CMS 禁用了 UseAdaptiveSizePolicy 新生代的内存动态变化策略,避免过多数据进入老年代

开启UseAdaptiveSizePolicy

高并发访问后

重点关注

  • Tomca 线程数目最大值设置数目过大,瞬时高峰导致创建过多线程可能导致OOM
  • Tomcat 业务处理线程,并非启动时创建。是在第一个http 请求到来时开始创建(最多一次创建50个线程。未达到核心线程时,第二次访问再创建),故前端开始放流时,会出现短暂的CPU使用率增高
  • GC 算法选择,不推荐使用默认的 -XX:+UseAdaptiveSizePolicyJdk5 默认开启) 其目的为了增大JVM 吞吐量。开启后 新生代 egen:s0:s1 不再遵守指定的比例大小(4:1:1)而是采用JVM自己动态变化的新生代的分层大小。故开启后 s1,s2 很可能被压缩的很小,导致youngGc时,大量的对象被压倒 老年代 ,进而产生频繁full gc 推荐使用Jdk8 推荐 CMSJdk9+ 使用G1 jdk9 g1商用化)
  • Jvm 内存大小设置,推荐堆的总体大小1g+ ,新生代不少于堆总体一半,s1,s2 的容量不要过小,避免 yongGc 时因对象过大,而直接进入老年代

监控工具

  1.  pinpoint (运维部门已支持),可以监控jvm 的大至堆使用,fullgc 的频率
  1. Arthas 用于堆使用详情,gc的详情,以及动态修改jvm 部分参数
  1. visualVm 用于开发环境测试(推荐 安装 visualgc 插件,监控gc详情)

visualVm  本机使用观察注意项

下载 不推荐 Oracle jdk8 自带的。

推荐下载的插件

Java 启动的参数信息

仪表盘信息,总体介绍 jvm 使用信息

当前线程使用情况

支持sample 支持监控特定时间的 java 的内存cpu 使用情况

profile  可用监控 项目中 sql 的使用信息,cpu,锁 等信息监控,(开源版存在,Oracle版无)

常用jdk8 JVM参数

Plaintext

-Xmx1g
-Xms1g
:初始堆大小直接等于最大堆大小
-Xmn512m         新生代大小
-XX:MetaspaceSize=128m        元空间初始大小
-XX:MaxMetaspaceSize=320m         元空间最大值
-XX:+UseConcMarkSweepGC        尽量使用CMS收集器,降低GC停顿时间
-XX:+UseCMSCompactAtFullCollection        使用并发收集器时,开启对年老代的压缩.
-XX:CMSInitiatingOccupancyFraction        使用cms作为垃圾回收使用70%后开始CMS收集
-XX:+CMSParallelRemarkEnabled        降低标记停顿
-XX:SoftRefLRUPolicyMSPerMB=0 避免元空间fullgc时 class被清理 导致重新加载
-XX:SurvivorRatio=8 新生代 内存分区 6:1:1
-verbose:gc 在控制台输出GC情况
-XX:+PrintGCDetails 在控制台输出详细的GC情况
-XX:+PrintGCDateStamps GC的打印基于日期的时间戳
-XX:-OmitStackTraceInFastThrow  避免打印同样错误日志到一定次数就会被jvm默认优化掉。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=C:/Users/liaojinlong/Downloads/dump/heap/(文件路径-保证目录存在)

-XX:+UseStringDeduplication 合并重复字符串
-XX:+DisableExplicitGC 禁用代码中System.gc()
-XX:-UseAdaptiveSizePolicy 关闭内存分配策略自适应 保证 edgen:s1:s2 8L1:1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值