ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Tomcat 10.0.13 Windows x64 部署避坑指南:jakarta迁移与调优

Tomcat 10.0.13 Windows x64 部署避坑指南:jakarta迁移与调优

简介:Tomcat 10.0.13 官方版是面向64位Windows系统的Java应用服务器,适合Web开发者与日常运维人员用于Servlet/JSP项目调试、测试和小规模应用部署。压缩包共644个文件,约12.58MB,以HTML文档、字节码、JSP页面、源码等类型为主,便于查看说明、阅读代码或复用内建示例。作为官方发行版,它整合了Servlet容器、JSP引擎和连接器等核心组件,并附带多种启动与服务管理脚本、默认应用部署目录、安全与性能优化配置;借助这些内容,可较快掌握目录结构、虚拟主机配置、连接器调整以及安全部署要点。包内各功能子目录规划清晰,且涵盖日志、临时文件等辅助目录,适合对照理解不同路径的作用。截至目前已有275人学习下载,适合初学者建立从安装到调优的完整认知,也适合有经验者对照源码研究内部运行机制。

1. apache-tomcat-10.0.13-windows-x64:这套官方版到底解决什么问题

在 Windows x64 服务器上把 Java Web 应用跑起来,绕不开的一个基础件就是 apache-tomcat-10.0.13-windows-x64 官方版。它把 Servlet/JSP 应用变成可访问的 HTTP 服务,自带自动解压 war、热部署和基础监控,不需要额外装 Web 服务器就能独立上线。对刚接手 Java 项目的开发者,它解决的是“代码打了包之后,下一步交给谁跑”的问题;对已经在线上踩过坑的运维,看点在于 10.0 系列是个明确的分水岭:包名前缀从 javax 换成了 jakarta,很多旧 war 在这版上会直接翻车。这篇按“装、起、部署、调参、排错”的路径推进,命令到参数都给你抄,专治看一眼就犯困的官方文档。

2. 版本选型与目录结构:为什么是 10.0.13,解压出来先看哪儿

2.1 三个大版本选哪个:10.0 与 9.0、8.5 的分水岭

Tomcat 下载页面上常年扎堆 8.5、9.0、10.0 三个系列的 zip,不少人习惯性拉了最老那个,理由是“网上教程多”。这个习惯在 10.0.13 这里是灾难。10.0.x 实现的是 Servlet 5.0 和 Jakarta EE 9 规范,包名从 javax.* 全面迁到 jakarta.*。你手上的 war 如果是四五年前的 Spring 项目,用的多半还是 javax.servlet.http.HttpServlet,解压部署进去,日志刷一堆 ClassNotFoundException: javax.servlet,页面直接 404。

我一般这样选:老项目要短时间恢复正常,别犹豫,回退 9.0.x;新项目或者迟早要往 Jakarta 方向迁的,就留在 10.0 系列。表里这几个差异是最容易影响选型的:

大版本Servlet 规范包名前缀JDK 底线典型场景
8.53.1javax.*JDK 7 起老系统维护现场,HTTP/2 支持不完整
9.04.0javax.*JDK 8 起javax 生态里的稳定选择,资料最多
10.0.x5.0jakarta.*JDK 8 起、生产建议 11/17新项目优先,和未来版本接轨

选完版本再看 JDK:10.0.13 官方标注的下限是 JDK 8,但生产环境我建议至少 11 起步、能用 17 就用 17。别拿 JRE 当 JDK,下载时看到 “JRE” 选项一定避开,Tomcat 启动需要 JDK 里的编译器和工具链。版本选型这件事,记住一条就行:先看你项目的包名,apache-tomcat-10.0.13 认 jakarta。

2.2 解压后先认目录:conf、lib、webapps、work 分别管什么

zip 解压到 D:\tomcat-10.0.13 之后,里面七八个目录,新手容易乱的是 bin、conf、webapps、work 这四个,logs、temp 往往被忽略,直到排错时才发现没早看。给你一张我在笔记本上留的目录速查表:

目录核心内容什么时候动它
binstartup.bat、catalina.bat、configtest.bat、service.bat日常启动和排错都在这
confserver.xml、web.xml、context.xml、tomcat-users.xml、logging.properties端口、虚拟主机、角色权限都在这
lib各 jar 包别乱加乱删;应用公共库放这里
webappsROOT、manager、host-manager 和你的 warwar 自动解压地,部署主战场
workJSP 编译缓存与会话临时文件页面不更新时清理这个目录
logscatalina..log、localhost..log、manager.*.log排错第一现场
temp上传、序列化临时文件一般不用手动管

这里有个很容易误判的:work 不是留着好看的,JSP 第一次被访问时编译成 class 存在 work\Catalina\localhost\你的应用名\ 下面。你改了 JSP 重新部署,有时候浏览器还是旧页面,清 work 往往比清浏览器缓存更管用。temp 也常见翻车场景,被磁盘清理工具当垃圾清了,当时在跑的应用可能正在往里面写上传缓存文件,清的时候最好先停服务。

目录认完,再串一次请求路径,帮你把 conf 里的标签和目录对上:浏览器请求进来,先进 conf/server.xml 里的 Connector 端口,然后过 Engine,按域名匹配 Host,再按上下文路径落到 webapps 下的某个应用目录,最后按 web.xml 里的映射找到对应的 Servlet。所以“找不到页面”这类问题,排查顺序就该是端口通不通、域名匹配不匹配、目录在不在、web.xml 映射对不对。这条链路后面每一章都会反复用到。

还有一个新手容易搞混的:lib 目录不要顺手把 WEB-INF/lib 的依赖也塞进去。Tomcat 的 lib 是全局公共类库,塞多了会造成类冲突;应用自己的 jar 放自己 war 里的 WEB-INF/lib。两者的边界划不清,第 5 章的类冲突现象就是从这里来的。

3. 安装与启动:JDK 版本、环境变量和三种启动方式

3.1 先解决 JAVA_HOME:别用 JRE,也别装错位数

拿到 apache-tomcat-10.0.13-windows-x64.zip,先别急着解压,先确认机器上到底有没有能用的 JDK。打开 cmd,输入:

java -version

能正常打印版本,接着确认位数:

where java

where java 输出的路径一般长这样:C:\Program Files\Java\jdk-17\bin\java.exe。看到 Program Files (x86) 就要警惕,那很可能装的是 32 位 JDK,Tomcat 的 64 位 Windows 版在 32 位 JVM 上会有兼容性小毛病。

逻辑说明:java -version 看的是 PATH 里命中的那个 java,所以 where java 是找它到底在哪个目录,路径带不带 Program Files (x86),基本就能判断位数。另一个更稳的办法是看 JAVA_HOME 指向的目录名,jdk-17 这种命名一般不会骗人。

重点来了:Tomcat 的启动脚本 catalina.bat 找 Java 的顺序,是现看 JAVA_HOME 环境变量、再看 PATH 里的 java,JAVA_HOME 配错时它回头去 PATH 里捞一个,这就容易捞到乱七八糟的东西。我习惯在部署前先单独开一个命令行窗口,把 JAVA_HOME 指到这个项目要用的 JDK:

set JAVA_HOME=C:\Program Files\Java\jdk-17

这里一定要给到 JDK 的根目录,不是 bin 那一级,也不要带尾斜杠。我见过有人写成 JAVA_HOME=C:\Program Files\Java\jdk-17\bin,启动脚本找 %JAVA_HOME%\bin\java.exe 的时候直接翻车。JDK 版本建议用 11 的 LTS 或 17 的 LTS,别用最新版本追新,Tomcat 10.0.13 在高版本 JDK 上常见的一些反射告警你要额外处理,没必要在环境上给自己加戏。

如果全局环境变量里已经有一个旧 JAVA_HOME,像老项目强制要求 JDK 8 的情况,千万别顺手把系统的全局 JAVA_HOME 改了,那会影响别的服务。只在当前窗口 set JAVA_HOME,用完关窗口就行。如果每次都懒得手敲,把这几行存成 bin 下的 setenv.bat,Tomcat 启动时会自动读,这个机制我们留到第 6 章专门讲。

3.2 启动:startup.bat、catalina.bat run 和 configtest.bat

第一步永远是先做配置体检,别直接双击 startup.bat:

cd /d D:\tomcat-10.0.13\bin catalina.bat configtest

catalina.bat configtest 会加载 conf/server.xml 并解析一遍,语法有问题直接给你报出来,正常则输出成功提示。这一步 10 秒钟,省得你起一个起不来的服务再来回看日志。

接下来的启动方式有三条路,按需选:

startup.bat

这是最常用的,会在一个新窗口里拉起 Tomcat。但新窗口一闪而过是个经典翻车现场,容错很差,所以排查问题我从来不用它,改用:

catalina.bat run

catalina.bat run 在前台跑,所有启动日志直接刷到当前窗口,端口冲突、配置错误都逃不掉。确认一切正常、要正式部署时,再关掉前台、切回 startup.bat 让它作为后台服务跑。

逻辑说明:startup.bat 本质调用的还是 catalina.bat 的 start 参数;start 和 run 两个参数的核心差异是是否另开窗口和是否挂在当前进程。start 用于日常后台跑,run 用于排错时观察完整输出。实在要看后台窗口的日志,就去 logs 目录翻 catalina.*.log,名字带当天日期的那一份就是你要找的。如果控制台里中文乱码,先改 conf/logging.properties,把 encoding 显式改成 UTF-8,再重启。

启动成功与否的另一半判断是端口。浏览器访问 localhost:8080,能出默认欢迎页就算活了。注意第一次启动时 work 目录里会有新内容,这说明它已经开始把 JSP 编译进去了。

3.3 启动后第一件事:确认端口、进程与日志

服务本机访问正常,不等于部署环境安全。我每次换环境都会走一遍这三个命令:

netstat -ano | findstr :8080 curl -I http://localhost:8080/ tasklist | findstr java.exe

netstat 输出里 8080 那行,最后一列的 PID 就是监听进程;tasklist 用来核对 java.exe 对应的进程名和 PID 是否匹配;curl -I 则拿 HTTP 响应头,能看到 Server: Apache-Coyote/1.1 这样的信息,确认响应的是 Tomcat 而不是别的占坑服务。

逻辑说明:这套组合的本质是“端口、进程、HTTP 三层核对”。端口通了但 curl 打不出来,大概率在防火墙;端口不通先看进程在不在;进程在但响应头不是 Tomcat,就要怀疑端口被别的服务抢先占了。第 5 章还会展开其中两个高频场景。

4. 部署与调参:war 包上线、server.xml 端口和服务配置

4.1 部署 war 的三种方式:直拷、热部署、管理器

最朴素的方式:把编译好的 war 直接丢到 webapps 目录下:

copy /Y D:\apps\demo.war D:\tomcat-10.0.13\webapps\demo.war

Tomcat 检测到 war 后会异步解压成同名目录,日志里能看到部署记录。这里最容易被忽略的是时间戳:如果你覆盖的是一个同名 war,Tomcat 判断要不要重新部署的标准里有文件时间戳,同一个 war 吹进去可能不触发重部署,页面还是旧的。

所以覆盖更新我很少用 copy 覆盖,而是先删旧目录再放新包:

rmdir /S /Q D:\tomcat-10.0.13\webapps\demo del D:\tomcat-10.0.13\webapps\demo.war copy /Y D:\apps\demo.war D:\tomcat-10.0.13\webapps\demo.war

这套组合拳的核心逻辑:Tomcat 在 autoDeploy 模式下,war 是部署的种子,同名解压目录是运行时来源;两者不一致时经常出现半更新状态。先删干净再放新 war,让 Tomcat 走完整的“解压新 war → 加载新文档”流程。

如果你开了 manager 应用(默认自带,但需要配角色),还有更可控的第三种方式,走文本接口部署:

curl -u admin:你的密码 "http://localhost:8080/manager/text/deploy?path=/demo&war=file:/D:/apps/demo.war"

这里的 path 是应用的上下文路径,war 参数是本地文件绝对路径。要能用这条命令,conf/tomcat-users.xml 里必须先给这个用户配上 manager-script 角色。路径写错会被管理器拒掉,返回 403 或 404,别急着怀疑权限,先看 path 和 war 拼得对不对。

4.2 server.xml 骨架与常用参数

部署进去只是第一步,想让服务符合生产预期,就得动 conf/server.xml。下面这个是我常用的最小可用骨架,去掉了一堆注释:

<Server port="8005" shutdown="SHUTDOWN"> <Service name="Catalina"> <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" maxThreads="300" acceptCount="200" maxParameterCount="10000" redirectPort="8443" /> <Engine name="Catalina" defaultHost="localhost"> <Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true" /> </Engine> </Service> </Server>

逻辑说明:Server 节点的 port 8005 是 Tomcat 自己用来接收关闭命令的管理端口,不是对外服务的业务端口;Connector 才是真正对外的 HTTP 入口,port、maxThreads、acceptCount 这些值都落在它身上。Engine 负责路由,Host 负责按域名选择应用集合,appBase 指向 webapps 目录。如果你只有一个应用、不需要单机多域名,理解到这个层级就够用了。

常被人反复调的就是 Connector 里的几个参数,我按常用默认到推荐值整理成一张表:

参数默认值含义我这边的取值
port8080对外 HTTP 端口按业务分配,别只用默认
connectionTimeout20000等待请求头的毫秒数小幅调小防慢连接占线程
maxThreads200最大工作线程数100~400,观察连接峰值再定
acceptCount100排队等待的请求数峰值高就加到 200~300
maxParameterCount10000单个请求最多参数数保持默认,防参数型攻击
redirectPort8443SSL 重定向端口无 SSL 就保持默认

maxThreads 不是越大越好,Windows 下端到端线程切换开销在那里,我见过一台 4 核 8G 的测试机上把 maxThreads 拉到 1000 结果响应反而变慢的。调这块的原则是先采用保守值,再拿压测结果逐步加。

Connector 还有一个地方要留意,address。默认不写 address 相当于绑定 0.0.0.0,所有网卡都监听;写成 127.0.0.1 就只有本机能访问。有些安全规范要求内网服务不许对公网暴露,就把 address 改成内网网卡 IP。判断外网访问不到的故障时,这一项和防火墙要一起查。

8005 的管理端口我一般建议不要暴露到公网,不需要远程通过 8005 关服务的话,保持默认且让防火墙拦住。还有 AJP 8009 那一段,除非真有反代场景要用 AJP 协议,否则保持注释状态,别为了“多一个端口安心”把它解开。

4.3 单机双实例:共享安装包 + CATALINA_BASE

一台机器要跑两个不同端口、互不影响的 Tomcat 服务,并不需要解压两份安装包。正确姿势是共用一个安装目录,各实例用自己的 CATALINA_BASE。

set CATALINA_HOME=D:\tomcat-10.0.13 set CATALINA_BASE=D:\tc-node2 cd /d D:\tomcat-10.0.13\bin startup.bat

逻辑说明:CATALINA_HOME 指向安装包里的 bin、lib 等公共文件,CATALINA_BASE 指向实例自己的 conf、webapps、logs、work、temp。启动脚本会优先用 CATALINA_BASE 下的配置去跑,相当于同一个程序骨架,两套内脏。

这样做的核心步骤是给 node2 准备独立的 conf/server.xml,至少改三个端口:对外 8080 改成 8081,管理端口 8005 改成 8006,AJP 8009 改成 8010(如果开了的话)。不改的话第二个实例一启动,端口冲突直接把第一个实例一起带崩。这套做法我在测试环境区分“预发/测试”时用得很勤,比复制整个目录省心,也方便同时更新公共 jar。

5. 避坑排查:10.0 系列最容易翻车的五个现场

5.1 启动闪退:双击 startup.bat 黑窗一闪而过

现象:点一下 startup.bat,窗口瞬间关闭,服务根本没起来;看任务管理器也没有 java.exe。

原因:多数是 JAVA_HOME 没配好,脚本在找不到 JDK 时直接退出。少数情况是配置里的路径带引号或带 bin 后缀,导致 java 命令解析失败。

解决:别双击,打开 cmd 手动执行:

cd /d D:\tomcat-10.0.13\bin catalina.bat run

错误会直接打在窗口里,照着改。改完先跑 catalina.bat configtest 确认配置,再正常启动。逻辑说明:run 模式把异常打印出来,闪退的根因就现形了;configtest 则是防二次翻车的前置体检。这个习惯养成了,后面所有 Tomcat 配置问题都能省一半时间。

5.2 端口被占:Address already in use

现象:启动日志出现 java.net.BindException: Address already in use: NET_Bind,8080 起不来;或访问 8080 返回的不是 Tomcat。

原因:别的服务(常见如 IIS、Nginx、另一个 Tomcat)把 8080 占了。Tomcat 没退干净时,残留的 java.exe 也干这事。

解决:先找占用者,再决定杀进程还是改端口:

netstat -ano | findstr :8080 tasklist /fi "PID eq 1234"

确认占用进程属于残留 Tomcat 后,用 taskkill /F /PID 1234 强制结束;要是系统服务占的,就直接把 server.xml 里端口改成 8081 更省事。注意改端口后,防火墙放行规则和应用的访问 URL 都要跟着改。逻辑说明:netstat 给出的 PID 是线索,tasklist 是核对身份,两个命令配合才不会误杀系统进程。

5.3 旧页面:war 覆盖后还在加载老版本

现象:明明把新 war 放进 webapps,刷新还是旧界面,日志里也没有重新解压记录。

原因:Tomcat 的 autoDeploy 会结合 war 时间戳判断是否需要重新部署,同一文件名、时间戳变化不明显时容易漏掉;另外 work 目录里旧的 JSP 编译产物会干扰。

解决:先删旧目录再放新 war,并清掉 work 对应项,命令在第 4 章给过。嫌手滑,走 manager 的 undeploy 和 deploy 两条指令更干净:

curl -u admin:你的密码 "http://localhost:8080/manager/text/undeploy?path=/demo" curl -u admin:你的密码 "http://localhost:8080/manager/text/deploy?path=/demo&war=file:/D:/apps/demo.war"

逻辑说明:undeploy 会把 webapps 下对应的解压目录和运行时状态一并清掉,再 deploy 就让 Tomcat 从头走一遍部署流程,彻底绕开时间戳判断这个黑匣子。从那以后我每次升级 war,默认就走这套组合,基本没再见过“改了没生效”。

5.4 远程不通:本机通、外网 IP 不通

现象:在服务器本机 localhost:8080 能出页面,换办公室网络用公网或内网 IP 访问直接超时。

原因:Windows 防火墙默认拦入站 8080;云服务器还要看平台安全组规则;再一个是 server.xml 的 Connector address 被写成了 127.0.0.1。

解决:按这个顺序查。先确认监听地址:

netstat -ano | findstr :8080

看监听地址那列是 0.0.0.0 还是 127.0.0.1,是 127 就去改 server.xml。是 0.0.0.0 就配防火墙:

netsh advfirewall firewall add rule name="Tomcat-8080" dir=in action=allow protocol=TCP localport=8080

云环境还要去控制台加安全组入站规则,这一步最容易被忽略。逻辑说明:本机通、外网不通的排查队列是监听地址 → 本机防火墙 → 平台安全组,顺序不能反,反了你可能对着防火墙折腾了半天,最后发现是安全组没放行。

5.5 老 war 部署即 404:javax 与 jakarta 的命名空间之战

现象:把 9.0 时代的老 war(尤其 Spring 4、老 Servlet 项目)放到 10.0.13,日志里刷 ClassNotFoundException: javax.servlet.http.HttpServlet,页面直接 404。

原因:10.0 系列的规范包名全面迁到 jakarta.,旧 war 里的 import 还指向 javax.。不搞清楚这一点,部署多少次都是白搭。

解决:两类手段。想继续留在 10.0,就把源码的 import 批量替换,再换依赖。典型替换命令:

Get-ChildItem -Path .\src -Recurse -Filter *.java | ForEach-Object { (Get-Content $_.FullName -Raw) -replace 'javax\.servlet', 'jakarta.servlet' | Set-Content $_.FullName -Encoding UTF8 }

对应的 Maven 依赖也要同步改:

<dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> <version>5.0.0</version> <scope>provided</scope> </dependency>

逻辑说明:注意只替换 javax.servlet 前缀,别把别的 javax 库一并改了,scope 用 provided 是因为 Tomcat 本身已经带了这套 API,打进 war 反而会产生类冲突。不想折腾代码就退回 Tomcat 9.0.x,老项目最省事的路。这个坑的根因不在 Tomcat,而在于项目本身是照着老 Servlet 规范写的,10.0.13 只是诚实执行了新规范。

6. 黑匣子变白盒:setenv.bat 与日志验证这套组合拳

6.1 用 setenv.bat 固化 JVM 参数

Tomcat 的 bin 下如果有 setenv.bat,startup.bat 和 catalina.bat 都会自动加载它,这是给环境参数留的口子,比在系统环境变量里堆全局变量干净。我的一个常用模板:

set "CATALINA_OPTS=-Xms512m -Xmx2048m -Dfile.encoding=UTF-8 -Djava.awt.headless=true"

逻辑说明:这里用 CATALINA_OPTS 而不是 JAVA_OPTS,CATALINA_OPTS 只注入到启动 Tomcat 的进程,JAVA_OPTS 会用于脚本的其他分支;堆参数放 CATALINA_OPTS,避免污染其他命令。Xms 和 Xmx 是初始和最大堆内存,按机器实际内存调整,512M/2048M 对多数单应用来说是够用的起点。

6.2 启动后两分钟,日志和请求都值得走一遍

启动成功后确认服务真的健康,我一般做下面三步:

catalina.bat configtest catalina.bat run

正常打印 “Server startup in [xxxx] milliseconds” 后,另开窗口:

curl -I http://localhost:8080/ curl -s -o NUL -w "%{http_code}" http://localhost:8080/demo/

curl -w 输出状态码,200/302 是正常,404/503 直接进 logs 看 localhost.*.log;配合 PowerShell 的 Test-NetConnection localhost -Port 8080 可以把端口层也过一遍。逻辑说明:configtest 负责配置层、run 负责启动层、curl 负责请求层,三层都过了才算部署完成,而不是看到窗口弹出来就收工。

以前我部署完 Tomcat 只看一眼窗口在不在,结果好几次应用是 404 状态跑了一整天才被发现。从那以后,我每次换机器、换版本、换 JDK,都强制走一遍 configtest → run → curl 状态码确认的顺序,这套流程救了我很多次,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进