简介:Apache Tomcat v8.5.16 是经典的 Java Web 应用服务器版本,面向需要部署 Servlet/JSP 项目的开发与运维人员,可在本地快速搭建 HTTP 服务环境,或作为理解 Tomcat 目录结构、启动流程与配置方式的入门素材。压缩包共包含 637 个文件,约 9.53MB,其中既有 html 静态页面与 jsp/jspx 动态页面示例,也有 java/class 源码与编译产物,以及 jar 依赖库、xml 配置、sh/bat 启动脚本等,基本覆盖 Tomcat 运行与二次开发所涉及的常见文件类型。资源包保留完整目录层级,bin 下提供启停脚本,conf 集中管理核心 XML 配置文件,webapps 内含可直接部署的示例应用,lib 集中存放第三方依赖包,logs 负责输出运行日志便于排查问题,方便读者对照官方结构逐层学习,或直接拷贝至服务器使用。整体包体精简,结构清晰,便于按需取用。目前已有 217 人浏览学习,适合初学 Java Web 部署或希望快速获得免编译 Tomcat 环境的用户下载参考。
1. 一个 zip 包引发的启动失败:Apache Tomcat v8.5.16 到底该怎么落地
接手一台业务服务器时,最先见到的常常是那个解压后连窗口都留不住的startup.bat——不少同事第一次碰 Apache Tomcat v8.5.16.zip 时,开场都是双击脚本、黑窗一闪、然后一脸茫然。这个 zip 包是 Tomcat 8.5 系列的一个发行压缩包,8.5.16 是版本号,落在服务层面解决两类问题:老项目升不到太激进的运行时,新项目又需要一个和 JDK 8 配合默契的 Servlet 容器。它支持 Servlet 3.1 规范、JSP 2.3,使用门槛低,也是很多遗留业务系统的稳妥选择。这篇文章会把「一个有版本号的 zip 包」拆成「能部署业务、能调参数、能排查故障的运行环境」,新手可以按步骤跟到服务启动,维护旧服务的老手也能在部署边界和排错路径上找到验证点。
2. 从 zip 包到可运行服务:目录结构、环境检查与版本选型
2.1 为什么选 8.5.16:兼容性与生命周期的边界
标题里的版本号不是随便拼出来的。Tomcat 8.5.x 处在 8.0 到 9.0 之间的过渡段,规范上对齐 Servlet 3.1,常见搭配是 JDK 7 或 JDK 8,而 9.0 起底线提到 JDK 8,到了 Tomcat 10 则是 Jakarta EE 9 的命名空间变更,javax.*直接换成jakarta.*。对拿着一堆老 war 包的业务系统来说,升级到 10 意味着改包名、改依赖、改源码引用,成本远高于换一个容器版本。8.5.16 作为 8.5 系列里相对稳定的落点,能让你保留旧代码的同时,吃到 NIO 连接器和更好的并发处理能力。
选型时我一般先问三件事:项目里用的 JDK 是哪个大版本、代码里是否还大量出现javax.servlet.*、有没有必须依赖旧 Tomcat 特有配置的部署脚本。只要这三条的答案偏「保守」,8.5.16 就是比最新大版本更省事的选项。下面这张表可以帮你在选型时少走弯路:
| 对比项 | Tomcat 8.5.x | Tomcat 9.0.x |
|---|---|---|
| Servlet 规范 | 3.1 | 4.0 |
| JSP 规范 | 2.3 | 2.3 |
| 最低 JDK 要求 | JDK 7 | JDK 8 |
| 典型使用场景 | 老系统升级、JDK8 环境 | 新项目、内部管理端 |
要注意的是,8.5.16 不是「越老越稳」,而是「和你现有环境越匹配越稳」。如果新项目刚立项、JDK 已经 17 甚至更高,硬塞 8.5.16 反而要处理很多老旧依赖的编译问题,这时候选新版本更合适。版本本身只是工具,匹配才是核心决策点。
2.2 解压后必须检查的目录与关键文件
拿到apache-tomcat-8.5.16.zip,第一步不是双击启动,而是把压缩包解开后逐项确认目录结构。Tomcat 的目录设计很固定,解压后你会看到七个核心目录,各自职责不同,乱改任意一个都可能让服务起不来。我维护服务器时通常先跑一遍ls -l看权限,再确认配置文件有没有被之前的部署者改动过。
| 目录 | 作用 | 维护注意 |
|---|---|---|
| bin | 启动 / 停止脚本,如startup.sh、shutdown.sh | Linux 下要检查脚本可执行权限 |
| conf | 核心配置文件server.xml、web.xml、context.xml | 改动前先备份,server.xml出错会导致启动失败 |
| lib | Tomcat 公共类库,全局 jar 都放这里 | 不要随意替换,应用专用 jar 放应用内 |
| logs | 运行日志、访问日志、localhost 日志 | 排错第一入口,启动失败先看这里 |
| temp | 运行时临时文件 | 可清理,不能删除目录本身 |
| webapps | 应用部署目录,war 包丢这里 | 自动解压,旧目录残留是 404 头号原因 |
| work | JSP 编译后的 class 文件 | 偶尔清空解决页面不更新问题 |
解压后我习惯先确认bin目录下有没有setenv.sh。这个文件正常情况下不存在,需要自己创建,Tomcat 启动时若发现它就会自动加载,用来固定 JVM 参数和运行环境变量。依赖默认参数跑业务的服务器,往往在内存配置或编码上踩坑,根因就是这里没有文件。
检查目录的同时,还要看一眼解压出来的路径里有没有空格。Windows 环境下把 zip 包解压到C:\Program Files\...这类带空格的路径,某些旧脚本会因路径解析问题翻车。常见做法是把 Tomcat 放到/opt/tomcat或D:\tomcat这类无空格的目录,能少掉一批奇怪的环境类报错。
2.3 最小化启动前的三项环境检查
在敲任何启动命令之前,先把环境变量确认清楚。Tomcat 本身是用 Java 写的,找不到JAVA_HOME时的表现很迷惑:有时窗口一闪而过,有时报Neither the JAVA_HOME nor the JRE_HOME environment variable is defined,有时却假装一切正常但启动页打不开。我在新环境上部署时,会用一个长命令把关键信息一次性打出来:
echo "JAVA_HOME=$JAVA_HOME" java -version 2>&1 if [ -z "$CATALINA_HOME" ]; then echo "CATALINA_HOME is empty, use script current path" export CATALINA_HOME=$(cd "$(dirname "$0")/.." && pwd) fi grep -n 'port="8080"' "$CATALINA_HOME/conf/server.xml"这段脚本做的事情很直接:先打印JAVA_HOME,再输出 Java 版本确认位数和版本号,然后判断CATALINA_HOME是否为空,为空时用当前脚本的上层目录推算,最后在server.xml里找默认端口。grep -n加了行号,端口冲突排查时你能直接定位到第几行。这里的port="8080"是 Tomcat 默认 HTTP 端口,如果你改过端口配置,就要换成本项目实际的端口。
另一个容易忽略的检查项是端口是否已被占用。Linux 下用ss -lntp | grep 8080,Windows 下用netstat -ano | findstr 8080,看到LISTENING状态就说明端口被占,强行启动只会换一个启动失败的方式。环境检查这三步做好之后,才轮到真正的启动环节。
3. 启动、停止与 JVM 参数:让 Tomcat 8.5.16 不再一闪而过
3.1 前台启动与后台启动:什么时候用哪一种
Tomcat 8.5.16 的启动脚本有两个形态,前台启动和后台启动,它们解决的是完全不同的问题。前台启动用catalina.sh run,终端会一直挂着,所有日志直接刷在当前窗口,适合第一次跑、排错、看启动顺序;后台启动用startup.sh,脚本执行完就返回,应用跑在后台。生产服务器上几乎没人用前台模式,因为终端一关服务就跟着没了。
# 前台启动,日志实时刷屏,Ctrl+C 可停止 $CATALINA_HOME/bin/catalina.sh run # 后台启动,脚本返回即认为已提交,服务由 Tomcat 进程持有 $CATALINA_HOME/bin/startup.sh # 启动后立刻看日志,确认没有异常 tail -f $CATALINA_HOME/logs/catalina.outtail -f是启动后最值得养成习惯的命令。后台启动不像前台那样把日志打到终端,所有输出集中在logs/catalina.out。我见过不少新手启动完直接去浏览器访问,访问失败再跑回来看日志,其实startup.sh返回成功并不代表 Tomcat 启动成功,它只代表脚本被正确执行了。真正判断成功与否,要看日志里有没有Server startup in这一行,以及netstat里 8080 端口有没有进入LISTENING状态。Windows 环境下用catalina.bat run代替,双击startup.bat一闪而过的原因基本都出在JAVA_HOME没配好,用前台模式能立刻看到具体报错。
3.2 用 setenv.sh 固定 JVM 参数:堆内存、GC 与编码
Tomcat 启动时的 JVM 参数不在server.xml里,而在启动脚本加载的环境变量里。最好的做法是不去改catalina.sh本体,而是创建bin/setenv.sh,Tomcat 启动时自动检测并执行它。这样升级 Tomcat 小版本时,你的参数配置还能继续复用,不用反复去对比脚本差异。
#!/bin/bash JAVA_OPTS="-Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8" CATALINA_OPTS="-Xms512m -Xmx2048m \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC -Djava.awt.headless=true" export JAVA_OPTS CATALINA_OPTS这里把参数拆成两组是有讲究的。JAVA_OPTS会传给所有由脚本启动的 Java 进程,包括shutdown.sh;CATALINA_OPTS只在 Tomcat 主进程启动时生效。编码类参数放JAVA_OPTS,堆内存和 GC 参数放CATALINA_OPTS。-Dfile.encoding=UTF-8解决的是日志和文件读写的中文乱码,-Dsun.jnu.encoding=UTF-8负责文件名编码,这两个在 Windows 和 Linux 下的表现差异很大,统一强制指定能减少很多跨平台部署的玄学问题。
堆内存参数里,-Xms512m表示初始堆大小,-Xmx2048m表示最大堆大小,这两个值通常设置成一样,避免运行中频繁扩容造成停顿。Metaspace 是 JDK 8 之后替代 PermGen 的元数据区,默认只受物理内存限制,不设上限的话,在 JSP 页面特别多、动态生成类的系统里容易把内存吃干净,所以显式设置-XX:MaxMetaspaceSize=512m更可控。-XX:+UseG1GC是 GC 策略,JDK 8 下可以用,如果你的系统是响应式并发比较高的业务,G1 比默认的 Parallel GC 更平滑。别一股脑把-Xmx顶到机器物理内存的 80%,Tomcat 还要留内存给操作系统文件缓存和连接池,给系统留四分之一到三分之一是常见做法。
3.3 停止与自启:让服务可管理
停止 Tomcat 也有正确的姿势。shutdown.sh会向 Tomcat 发送关闭指令,进程收到后开始清理线程池、关闭连接器、释放资源,然后退出。这是推荐的方式。很多人部署脚本里写的是pkill -9 java,这类强杀命令虽然能立刻释放端口,但会让正在处理的请求断裂,连接池状态也可能不一致,长期这么干迟早会把线上数据搞乱。如果出现shutdown.sh命令执行了但端口还占着,先去查logs/catalina.out末尾是否有卡住的线程,不要急着补一发kill -9。
生产环境里 Tomcat 一般要交给进程守护工具管理,Linux 下最常用的是 systemd。为 8.5.16 写一个服务单元能同时解决开机自启和异常退出后的拉起。下面这份配置是常见做法,路径和用户请按实际环境替换:
[Unit] Description=Tomcat 8.5.16 Application Server After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=JAVA_HOME=/usr/lib/jvm/java-8 Environment=CATALINA_HOME=/opt/tomcat Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid ExecStart=/opt/tomcat/bin/startup.sh ExecStop=/opt/tomcat/bin/shutdown.sh [Install] WantedBy=multi-user.targetType=forking表示启动脚本会 fork 出实际服务进程,systemd 通过CATALINA_PID指定的 PID 文件确认主进程身份;ExecStop指向shutdown.sh,保证停止时走优雅关闭流程;User=tomcat是很重要的一行,绝对不要用 root 跑 Tomcat,一旦应用被攻破,root 权限会直接落到攻击者手里。配置写好后执行systemctl daemon-reload再systemctl enable tomcat,重启机器后 Tomcat 会自动拉起,配合systemctl status tomcat查看运行状态,管理体验比手写脚本好出一截。
配置 systemd 服务单元时还要注意:JAVA_HOME和CATALINA_HOME这两个环境变量在 systemd 环境下不会自动继承 shell 配置,必须显式写在单元文件里,否则启动脚本会因为找不到 Java 或找不到 Tomcat 目录直接报错退出。这也是很多人把 Tomcat 放进 systemd 后反而启动失败的常见原因。
4. 应用部署的三种姿势:war 包、数据源与虚拟主机
4.1 war 包热部署:放进去就能跑吗
Tomcat 8.5.16 最常见的部署方式就是把 war 包丢进webapps目录。server.xml里 Host 元素默认开启了autoDeploy和deployOnStartup,Tomcat 在运行期间能自动扫描新放入的 war 包并完成解压部署,这就是「热部署」感觉的来源。但「放进去就能跑」这句话有一个前提:war 包的命名就是应用的上下文路径。
# 进入部署目录 cd $CATALINA_HOME/webapps # 备份旧包,避免改出问题没有后悔药 cp sample.war sample.war.bak.$(date +%Y%m%d) # 删除旧解压目录,这一步不能省 rm -rf sample # 放入新包 cp /data/archive/sample.war . # 若 Tomcat 未开启热部署,手动重启后生效 $CATALINA_HOME/bin/shutdown.sh $CATALINA_HOME/bin/startup.shsample.war放入后,访问路径是http://host:8080/sample/,去掉 war 扩展名的部分就是 context path。更新版本时最值得强调的是rm -rf sample这步:Tomcat 解压 war 包之后,webapps下会同时存在sample.war和sample/目录,Tomcat 的默认逻辑是「目录存在就不重新解压」,所以如果只覆盖 war 包而保留旧目录,你会看到应用已经更新,实际跑的还是旧代码。这是应用升级不生效的头号原因。如果系统里 JSP 编译缓存异常,清空work目录再重启也能解决页面不更新的问题。
4.2 数据源与连接池:应用连不上数据库的常见根因
Java Web 应用连数据库最常见的坑,不是 SQL 写错,而是数据源配置没落到 Tomcat 能加载的位置。Tomcat 8.5.16 支持在应用或全局层面配置 JNDI 数据源,其中全局配置写在conf/context.xml,单应用配置写在META-INF/context.xml里。两者的区别是作用范围:全局数据源所有应用都可引用,单应用数据源只对当前应用可见。生产环境里我偏向用单应用配置,应用挪到别的环境时配置一起走,不容易污染其他项目。
<Context> <Resource name="jdbc/appDS" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/appdb?useUnicode=true&characterEncoding=UTF-8" username="app" password="change-me" maxTotal="20" maxIdle="10" maxWaitMillis="10000"/> </Context>参数说明是这里的关键。maxTotal是连接池允许的最大连接数,maxIdle是空闲连接数上限,maxWaitMillis是拿不到连接时等待的毫秒数,超过就抛异常。三个参数配好之后,应用通过 JNDI 名称jdbc/appDS获取连接,Tomcat 负责维护连接池的创建、复用和回收。如果应用一直报「无法创建 JDBC 驱动」或ClassNotFoundException,十有八九是驱动 jar 放错了位置。Tomcat 的公共类库里没有 MySQL 驱动,你得把驱动 jar 放到$CATALINA_HOME/lib或应用的WEB-INF/lib。放在应用内时,驱动类的加载隔离更干净;放在全局时,所有应用都能共用,选哪种取决于你维护的实例数量。
数据源配好后还有一个隐藏配置点:应用内部的web.xml需要声明资源引用。没有这一步,代码里lookup("java:comp/env/jdbc/appDS")会找不到对应资源。
<resource-ref> <res-ref-name>jdbc/appDS</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>res-auth写Container表示连接由 Tomcat 容器管理认证和事务,应用只负责借连接。如果这里写错,应用能启动,但访问数据库的每一个请求都可能报Name jdbc/appDS is not bound in this Context。
4.3 通过 server.xml 配置虚拟主机与上下文
一台 Tomcat 上跑多个业务域名,靠的是server.xml里的 Host 元素。Host 代表一个虚拟主机,name属性是访问者的请求域名,appBase是这个虚拟主机自己的应用目录。Tomcat 根据HTTP/1.1请求头里的Host字段判断该把请求交给哪个虚拟主机,所以多个 Host 共存时的name必须各不相同。
<Host name="app.example.com" appBase="webapps/app" unpackWARs="true" autoDeploy="true"> <Context path="" docBase="app" reloadable="false"/> </Host>这段配置的含义是:访问app.example.com根路径时,直接映射到webapps/app目录下的应用,即docBase="app"所指的应用名。path=""把应用绑定在域名根路径,访问http://app.example.com/就能进系统,不用再带上下文路径。reloadable="false"是生产环境推荐值,置为true时 Tomcat 会持续监听应用内 class 和 JSP 变化并自动重载,开发期好用,但生产环境会带来额外的扫描开销,甚至触发并发访问下的类加载异常。
配虚拟主机时还要注意appBase和docBase不要互相重叠。如果appBase是webapps/app,docBase又指向webapps/app,Tomcat 启动时会扫描到目录存在重复部署,日志里能看到一堆 context 启动失败的记录。常见的做法是:appBase指向一个独立目录,docBase用绝对路径指到该目录下的应用文件夹,两者保持清晰的从属关系。配置改动后必须重启 Tomcat 才会生效,server.xml不像应用那样支持热部署,别指望它自动加载新配置。
5. Tomcat 8.5.16 避坑指南:五个最容易翻车的地方
5.1 现象:启动后报端口被占用,进程却看不到
故障表现是startup.sh搞定后,catalina.out里出现一行The required Server component failed to start so Tomcat will be shut down,或 Java 抛异常说端口被占用,但ps -ef | grep java看不到可疑进程。这种情况在 Linux 和 Windows 都有,原因往往不是有人在跑 Tomcat,而是系统里有其他程序占用了 8080 或 8005 端口。8005 是关闭指令端口,一旦被占用,shutdown.sh也会失效。
解决方式是先锁定端口归属,再决定是换端口还是清理占用进程。Linux 下执行ss -lntp | grep 8080,Windows 下执行netstat -ano | findstr 8080,-lp参数会显示进程 PID。确认是无关进程后,要么kill掉它,要么改server.xml里的端口。改端口时要记得把 HTTP 端口、AJP 端口、关闭指令端口都看一眼,只改 8080 而 AJP 8009 仍被占,照样起不来。我处理这类问题时习惯先把conf/server.xml里的三处端口核对一遍,再决定改哪。
5.2 现象:war 包部署了,访问却一直 404
现象是webapps下面确实有刚放进去的sample.war,日志里也出现了解压记录,但浏览器访问http://ip:8080/sample始终 404。原因通常是旧解压目录没有清空,Tomcat 认为目录已经存在,不再从新 war 包解压,于是新旧文件混在一起,应用实际处于半旧状态。
解决方法是按 4.1 的顺序走一遍:停应用,删除同名解压目录,确认.war包时间戳是最新,重启 Tomcat。如果清空目录后仍然 404,去logs/localhost.$(date +%Y-%m-%d).log里查当前应用名的部署日志,里面会直接写出 context 加载成功或失败的原因,比如web.xml格式错误、缺少依赖类。这类 404 基本都不是 Tomcat 配置问题,而是应用自身状态的问题,排错时用日志倒推比猜快得多。
5.3 现象:JSP 页面中文乱码,改 server.xml 也没用
系统首页能打开,但所有中文显示成??或乱码,于是在server.xml的 Connector 上加了URIEncoding="UTF-8",重启后问题依旧。这个现象很典型:URIEncoding只处理请求地址里的参数编码,不处理 JSP 页面本身的输出编码。页面输出的乱码由 JSP 的pageEncoding和ContentType决定,两者不一致时浏览器按错误字符集解码,自然看到乱码。
正确做法是把 JSP 页面头部的编码声明统一成 UTF-8,同时让 Tomcat 在响应头里明确输出字符集。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>这两行要放在 JSP 页面文件最前面,pageEncoding告诉 Tomcat 读取这个文件时用什么字符集解码,contentType告诉浏览器用 UTF-8 渲染响应。如果请求参数本身也乱,再配合server.xml中 Connector 的URIEncoding="UTF-8",三层一起统一才能彻底解决。改动之后记得清空work目录,否则 JSP 编译缓存会继续按旧编码生成 class,改了页面也看不到效果。
5.4 现象:连接池报错 Cannot create JDBC driver of class
应用启动后控制台或日志里出现Cannot create JDBC driver of class 'com.mysql.jdbc.Driver',后面的异常堆栈经常是ClassNotFoundException或SQLException: No suitable driver。原因只有两类:驱动 jar 不存在,或驱动类名写错。MySQL 5.x 时代的驱动类名是com.mysql.jdbc.Driver,MySQL 8 之后的驱动把类名迁到了com.mysql.cj.jdbc.Driver,如果 jar 是 8 系的,配置却沿用 5 系的类名,抓手就是日志里那行找不到类。
解决方式是确认lib目录下驱动 jar 的真实包路径。用jar tf查看 jar 内容,找到Driver.class对应的全限定类名,再用这个类名去改context.xml。同时确认驱动 jar 放到了$CATALINA_HOME/lib下而不是webapps里某个应用的临时目录,后者会在 Tomcat 重启时被清理,造成「昨天还能连数据库,今天突然连不上」的间歇性故障。
cd $CATALINA_HOME/lib jar tf mysql-connector-java-xxx.jar | grep -i Driver.class5.5 现象:内存溢出 OutOfMemoryError: Metaspace
现象是运行几天后catalina.out里出现java.lang.OutOfMemoryError: Metaspace,系统响应变慢,重启后恢复正常,过几天又复发。原因在于 Tomcat 运行时会动态生成大量元数据——JSP 编译后的类、Spring 框架的 Bean 定义、自定义 ClassLoader 产生的类对象都放在 Metaspace。默认情况下 Metaspace 只受物理内存约束,当应用不停机持续运行,类加载越来越多,最终顶到系统内存上限。
解决方法是给 Metaspace 设置显式上限,并观察类加载趋势。Catalina启动时加载setenv.sh里添加两行参数:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m,前者是初始大小,后者是硬上限。设好后再用jstat -gc <pid>观察M区的使用率,如果总是贴近上限,说明 Metaspace 分配太小或存在类加载泄漏,再逐步上调并检查是不是每次热部署都在累积新的 ClassLoader。把reloadable="false"和限制 Metaspace 配合起来,生产环境的稳定性会明显提升。
6. 上线前检查清单与一个 JMX 小技巧:让 8.5.16 跑得更稳
6.1 锁死环境的两个习惯
上线前第一件事是把环境和版本锁死。Tomcat 8.5.16 搭配哪个 JDK,是 8 还是 7,启动脚本里JAVA_HOME指向哪里,这些必须写清楚。我见过最难受的现场是测试环境 JDK 8、生产环境 JDK 11,应用在测试没问题,上线报IllegalAccessError。Tomcat 8.5.16 对 JDK 版本的容忍度有限,锁死 JDK 版本能提前消灭一批只在特定版本下触发的问题。建议把版本信息写进部署说明的第一行,每次部署都先核对再动手。以下是我现在固定使用的检查清单,你也可以直接抄去用:
| 检查项 | 预期结果 | 验证命令 |
|---|---|---|
| JDK 版本 | 与开发、测试环境一致 | java -version |
| JAVA_HOME 路径 | 指向 JDK 安装目录 | echo $JAVA_HOME |
| 端口占用 | 8080 / 8005 / 8009 无监听 | ss -lntp |
| JVM 参数 | setenv.sh 已配置 | cat bin/setenv.sh |
| war 包备份 | 旧版本有备份 | ls webapps/*.bak* |
| 日志输出 | 无SEVERE级别异常 | grep SEVERE logs/catalina.out |
6.2 用 JMX 快速确认线程池状态
Tomcat 8.5.16 自带管理接口,通过 JMX 可以实时看到连接器线程池、队列长度、活跃线程数这些关键指标。启动时在setenv.sh里开启远程 JMX,观察生产环境才能拿到第一手数据。配置方式如下:
CATALINA_OPTS="$CATALINA_OPTS \ -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port=9010 \ -Dcom.sun.management.jmxremote.authenticate=false \ -Dcom.sun.management.jmxremote.ssl=false"开启后用本机的jconsole连接ip:9010,在 MBean 树里定位到Catalina下的ThreadPool,看currentThreadCount和currentThreadsBusy两个指标。如果currentThreadsBusy长期等于maxThreads,说明线程池被打满,要调大连接器maxThreads或检查应用里有没有慢查询阻塞请求。生产环境开启 JMX 一定要配合防火墙限制来源 IP,只允许运维跳板机访问 9010 端口,并建议把authenticate改成true配上账号密码,否则这个端口等同于裸奔。
6.3 回到第一性原理
Tomcat 8.5.16 这个 zip 包本身不复杂,复杂的是它承载的业务环境和部署习惯。我自己的习惯是每次部署都走一遍检查清单,不跳过任何一项,看起来繁琐,但能省下半夜被叫起来看日志的时间。记住 Tomcat 启动前端口确认、war 包更新前删旧目录、数据源驱动放对位置、JVM 参数固定显式配置、生产环境关掉 reloadable,这五条踩过的坑比任何教程都值钱。版本锁死、日志看满、JMX 盯着线程池,剩下的事情交给时间验证。希望帮到你。
本文还有配套的精品资源,点击获取