在Ubuntu上运行Tomcat时,默认安装配置通常只适合开发环境,一旦部署到生产环境并面临真实并发,就会出现各种性能瓶颈。例如,线程池默认最大200个线程,堆内存默认可能只有几百兆,系统文件描述符限制为1024,这些都远不能满足高并发请求。很多故障的根源不是应用代码,而是底层参数没有针对硬件和应用特征做出调整。本文会从JVM层、Tomcat连接器层和Ubuntu系统层三个维度,给出具体的调优参数和操作步骤,并说明每个参数为什么这么设置,方便你在自己的环境中对比调整。

一、JVM内存与垃圾收集器调优
Tomcat本质上是一个Java应用,所有请求处理都依赖JVM的内存分配和回收效率。Ubuntu下安装的OpenJDK版本通常较新,推荐使用JDK 11或17,因为它们对G1垃圾收集器的支持更成熟,而且Tomcat 9/10在较新JDK上能获得更好的I/O和并发表现。首先需要调整堆内存大小,-Xms和-Xmx最好设置为相同值,避免JVM在运行过程中动态扩容带来的STW停顿。例如一台16GB内存的Ubuntu服务器,如果只运行Tomcat,可以把堆内存设置为8GB到10GB,给操作系统和文件缓存留出足够空间。年轻代大小可以通过-Xmn或-XX:NewRatio指定,对于Web应用请求创建对象频繁,年轻代一般可以设大一些,比如堆的1/4到1/3,减少对象过早进入老年代。
垃圾收集器方面,G1是当前生产环境的默认优选,它能够在停顿时间和吞吐量之间取得平衡。-XX:MaxGCPauseMillis建议设置为200毫秒左右,如果应用对延迟特别敏感可以降到100毫秒以内,但过小的目标会导致频繁GC进而降低吞吐量。如果JDK版本支持ZGC(JDK 15以上),在超大堆(32GB以上)且要求极低延迟的场景下可以尝试,但需要充分测试。Metaspace在Java 8以后取代了永久代,如果应用动态加载类较多(比如使用反射、动态代理),需要调大-XX:MaxMetaspaceSize,否则可能触发频繁Full GC或OutOfMemoryError。
示例配置可以放在CATALINA_BASE/bin/setenv.sh中:
export JAVA_OPTS="-server -Xms8g -Xmx8g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/var/log/tomcat/gc.log -XX:+UseStringDeduplication"
这段配置固定堆大小为8GB,年轻代2GB,使用G1,开启GC日志输出方便后续分析。实际取值要根据物理内存和压测结果调整,不要直接照搬。
二、Tomcat连接器与线程池优化
Tomcat处理请求的入口是Connector组件,从Tomcat 8.5开始默认使用NIO协议,到Tomcat 9默认也保持NIO,但NIO2或APR在某些场景下能进一步提升性能。NIO2使用异步I/O,适合高并发长连接;APR通过JNI调用本地库,在静态文件传输和TLS加解密上性能更好。Ubuntu下使用APR需要安装libapr1-dev和libssl-dev,并编译本地库,比较麻烦。一般推荐使用NIO2,配置简单且能得到明显吞吐提升。修改conf/server.xml中的Connector节点,将protocol属性改为org.apache.coyote.http11.Http11Nio2Protocol。
线程池参数直接影响并发处理能力。maxThreads表示同时处理请求的最大工作线程数,默认200,对于4核8GB的服务器可以放到400到800,但线程数不是越大越好,过多线程会导致CPU上下文切换开销剧增。minSpareThreads是保持空闲的线程数,建议设置为25到50,避免突发流量时频繁创建线程。acceptCount表示当所有工作线程都忙时,操作系统和Tomcat能够排队等待的最大请求数,超过后连接会被拒绝,建议设置为200到500。maxConnections控制同时可以建立的连接数,通常要大于maxThreads + acceptCount,否则排队形同虚设。请求处理超时connectionTimeout建议设为3000毫秒,防止慢连接长期占用线程。
还可以定义共享线程池Executor,让多个Connector复用同一组线程,减少资源浪费。下面是一个优化后的Connector配置示例:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="600" minSpareThreads="50"
maxIdleTime="60000" prestartminSpareThreads="true"/>
<Connector executor="tomcatThreadPool"
port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
acceptCount="300"
maxConnections="10000"
connectionTimeout="3000"
enableLookups="false"
URIEncoding="UTF-8"
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/plain,application/json"
redirectPort="8443" />注意这里的enableLookups要设为false,关闭DNS反向查询,否则每个请求都会产生DNS解析开销。compression开启后对文本类响应进行gzip压缩,能显著减少带宽占用,但会消耗少量CPU,可以根据实际响应内容大小调整compressionMinSize。
三、Ubuntu系统资源与内核参数调整
Ubuntu默认对单个进程可打开的文件描述符数量有限制(通常是1024),Tomcat在高并发时需要为每个连接分配文件描述符和socket,如果限制过小,即使Tomcat线程数设置很高,也会遇到Too many open files错误。需要修改/etc/security/limits.conf,为运行Tomcat的用户添加以下内容:
tomcat soft nofile 65535 tomcat hard nofile 65535 tomcat soft nproc 32768 tomcat hard nproc 32768
如果通过systemd启动Tomcat,还需要在unit文件中加入LimitNOFILE=65535和LimitNPROC=32768,因为systemd不会自动读取limits.conf。修改后执行systemctl daemon-reload并重启服务。
内核网络参数也需要同步调整,否则TCP连接的处理能力会成为瓶颈。net.core.somaxconn是socket监听队列的最大长度,默认128,建议提高到4096;net.ipv4.tcp_tw_reuse设置为1允许复用TIME_WAIT状态的连接,减少短连接场景下的资源消耗;net.ipv4.tcp_fin_timeout可以降低到30秒,加快释放已关闭连接。编辑/etc/sysctl.conf或新建/etc/sysctl.d/99-tomcat.conf:
net.core.somaxconn = 4096 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_fin_timeout = 30 net.ipv4.tcp_max_syn_backlog = 8192 fs.file-max = 2000000
执行sysctl -p使其生效。另外建议关闭swap或降低swappiness,避免JVM堆内存被换出到磁盘导致停顿。Ubuntu下可以通过vm.swappiness=5来减少交换倾向,生产环境如果内存充足可以直接swapoff -a并注释掉fstab中的swap行,但需确认物理内存足够。
四、调优效果验证与持续监控
参数调优不是一次性的工作,必须通过压测和监控来验证效果,并根据实际指标反馈继续调整。可以使用ab、wrk或JMeter模拟并发请求,重点关注吞吐量(每秒请求数)、平均响应时间、错误率和CPU/内存使用率。例如在Ubuntu上用wrk -t10 -c500 -d60s http://127.0.0.1:8080/做60秒压测,观察Tomcat是否出现线程池拒绝或连接超时。如果压测时CPU已经打满,说明瓶颈在应用逻辑或线程数过多;如果CPU不高但响应变慢,可能是I/O阻塞或内存GC频繁。
JVM层面的监控要借助GC日志和JDK自带工具。前面配置的GC日志可以用gceasy.io这样的在线分析平台查看停顿时间和回收频率,也可以用命令jstat -gcutil [pid] 1000实时查看各代空间使用率和GC次数。Tomcat本身可以通过JMX暴露MBean指标,在setenv.sh中加入-Dcom.sun.management.jmxremote相关参数,使用JConsole或Prometheus JMX Exporter进行采集。对于线程池,可以查看日志中是否出现RejectedExecutionException或连接被拒的情况,如果出现则说明maxThreads或acceptCount不足,需要继续调大或优化应用缩短请求处理时间。
最终调优的合理顺序是:先确定硬件和预期负载水平,调整JVM堆内存和GC参数,再配置Tomcat连接器和线程池,最后优化Ubuntu系统限制。每一轮修改只改一个或两个参数,记录压测数据对比,避免多个变量混杂导致无法定位问题。这样得到的配置才能稳定运行在你的Ubuntu生产环境中。