ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Tomcat 9.0.45 Windows部署配置与排坑实践

Tomcat 9.0.45 Windows部署配置与排坑实践

简介:这是Apache Tomcat 9.0.45的Windows x64版安装包,适用于Java Web开发者、运维人员及在校学生搭建Servlet/JSP运行环境,满足从本地调试到服务器部署的基本需求。压缩包共642个文件,大小约12.68MB,内容涵盖HTML文档、Java类、JSP页面、依赖JAR包、XML配置以及bat/sh启动管理脚本等,目录结构遵循标准Tomcat布局,便于直接解压使用。其中catalina.bat、startup.bat、configtest.bat等脚本可协助完成启动、检查与配置验证。目前已有768人学习下载,适合需要快速获取稳定Tomcat环境的Windows用户,也可用于对比学习Tomcat内部目录组织与关键配置方式。

1. 还在纠结 apache-tomcat-9.0.45-windows-x64 能不能用:先想清楚你拿它跑什么

很多人在 Windows 上接手的第一个 Java Web 容器就是 apache-tomcat-9.0.45-windows-x64 这个包,看到「9.0.45」就下意识觉得是老版本、想找更新的替代品。实际上这个版本对应 Servlet 4.0 / JSP 2.3 规范,Java 8 到 Java 16 的 JDK 都能稳定跑,大量存量系统和教学环境到现在还在用它。它不是性能最强的,也不是安全补丁最新的,但胜在行为可预期、配置文档多、踩坑方案全,适合内网系统、固定 JDK 版本的旧项目迁移、以及不想折腾模块化容器的那类业务。

这篇文章按「先装起来 → 再配参数 → 再部署应用 → 最后排坑」的顺序讲,覆盖你在 Windows x64 上从解压到常驻服务的完整路径。我不会给你一堆官网文档的搬运内容,只讲我在真实环境里反复用过的配置和翻车记录。

2. 把 9.0.45 在 Windows x64 上跑起来:解压、环境变量与首次启动的最小路径

2.1 下载哪个文件、解压后目录结构长什么样

Tomcat 9.0.45 在 Windows 平台的发行包有两种形态:一种是 zip 压缩包,解压即用,适合自己控制启动方式;另一种是 32 位/64 位的 Service 安装包,装完直接注册成 Windows 服务。标题里带 windows-x64 的 zip 包是绝大多数开发者和运维的首选,因为它不写注册表、不污染系统,删掉整个目录就等于卸载,升级时也只需要换目录。

解压后你会看到七个一级目录和一个 LICENSE 文件,初次接触的人最容易混淆的是bin、conf、webapps这三个。bin里全是启动和关闭脚本,包括startup.bat、shutdown.bat、catalina.bat,还有给 Windows 服务用的service.bat和tomcat9w.exe;conf是全部配置的家,server.xml、web.xml、tomcat-users.xml都在这里;webapps是自动部署目录,war 包丢进去就能被识别。剩下的logs、temp、work是运行时产物,work目录存 JSP 编译后的 class 文件,Tomcat 自己管理,别手动清理它。

我一般会把 zip 包解压到类似D:\tools\apache-tomcat-9.0.45这种不带空格的路径下。路径里有空格不是不能用,但后续在批处理脚本里写引号时很容易漏,反而给自己挖坑。

2.2 JAVA_HOME 与 CATALINA_HOME:两个环境变量的作用边界

Tomcat 是 Java 写的,启动脚本本质上是java.exe加载一堆 class 文件,所以它必须知道两件事:JDK 装在哪、Tomcat 自己在哪。这两个信息分别对应JAVA_HOME和CATALINA_HOME。很多人只配JAVA_HOME,Tomcat 也能启动,因为catalina.bat会自动推断自己的位置;但一旦你要用service.bat注册 Windows 服务,或者用tomcat9w.exe调整服务内存,CATALINA_HOME缺失会导致服务注册成功但启动失败。

配置方式是在系统环境变量里新建两项,或者在 cmd 窗口里用setx写入。注意JAVA_HOME要指到 JDK 的根目录,不是bin目录,也不是jre目录。下面是常见做法:

setx JAVA_HOME "C:\Program Files\Java\jdk-11.0.21" setx CATALINA_HOME "D:\tools\apache-tomcat-9.0.45" setx PATH "%PATH%;%JAVA_HOME%\bin"

setx写的是用户级环境变量,写入后需要重新打开 cmd 窗口才生效。为什么强调 JDK 根目录而不是jre目录?因为 Tomcat 9 的 JSP 编译功能需要调用 JDK 里的javac,只有 JRE 的话启动没问题,但第一次访问 JSP 页面时会报Unable to find a javac compiler。这是新手最容易踩的一个隐性缺口,启动脚本不会提前检查。

配置完成后,验证命令是java -version,确认输出里的版本号和你期望的 JDK 一致。如果机器上装了好几个 JDK,PATH里靠前的那个会胜出,通常建议把%JAVA_HOME%\bin放在PATH的最前面。

2.3 用 startup.bat 启动并验证 8080 端口

环境变量配好后的第一步是从命令行启动,不要双击startup.bat,因为双击启动时如果报错,窗口会直接闪掉,你什么都看不到。正确做法是打开 cmd,切到 Tomcat 的bin目录,手动执行:

cd /d C:\tools\apache-tomcat-9.0.45\bin catalina.bat run

catalina.bat run是前台启动模式,日志会直接打在当前窗口里,Ctrl+C 可以停掉。这个模式适合第一次启动、排查问题,因为它把启动过程的每一行输出都留在了你眼前。注意这里有个细节:catalina.bat run停在当前窗口,而startup.bat会新弹一个窗口,所以排查阶段用前者。

看到类似org.apache.catalina.startup.Catalina start的行且没有SEVERE级别的报错,就说明起来了。然后在另一个 cmd 里验证:

curl http://localhost:8080

返回一段 HTML 且有If you're seeing this, you've successfully installed Tomcat字样,就成功了。如果 curl 报连接拒绝,先别急着怀疑 Tomcat,检查一下 8080 端口是不是被防火墙挡了,或者被别的程序占了。用netstat -ano | findstr :8080看监听状态,能看到LISTENING就说明端口是通的。

2.4 前台窗口与 Windows 服务:开发机和服务器上的不同选择

启动方式的选择取决于这台机器是开发机还是服务器。开发机上我用catalina.bat run,因为改完配置重启方便,日志就在眼前,IDE 里调试也能直接挂到进程上;服务器上则应该注册成 Windows 服务,否则一旦用户注销登录,Tomcat 的进程会被系统关掉,这是 Windows 服务器上最常见的「人走了服务就没了」事故。

同时要分清startup.bat和service.bat的差异。startup.bat启动的进程是挂在当前用户会话下的,关掉 cmd 窗口进程不一定退出,但注销登录肯定受影响;service.bat注册的服务由 Windows 服务管理器接管,可以设成开机自启,不依赖任何人登录系统。后面第六章会专门讲服务化的配置,这里先记住结论:开发用前台,生产用服务。

3. server.xml 的 4 个必调参数:端口、线程池、编码与虚拟主机

3.1 Connector 与 Executor:连接器往线程池拿线程的机制

conf/server.xml是 Tomcat 的核心配置文件,理解它的关键是分清Connector和Executor的关系。打个比方:Connector是门口的接待员,负责接受 HTTP 请求;Executor是后面的员工池,真正干活的人。默认配置里没配Executor,Connector会自己拉一个内部线程池,但参数藏在代码里,不方便控制。想要精细化调优,就得显式声明一个Executor并让Connector引用它。

<Service name="Catalina"> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="300" minSpareThreads="25" maxQueueSize="200"/> <Connector port="8080" protocol="HTTP/1.1" executor="tomcatThreadPool" connectionTimeout="20000" redirectPort="8443"/> </Service>

maxThreads决定线程池上限,默认 200,对大多数内部系统够用;minSpareThreads是空闲时保留的最小线程数,设太小会在请求突增时频繁创建线程。maxQueueSize是排队等待的请求数上限,超过这个数,后续请求直接拒绝。这几个参数配合 JVM 堆内存一起看——线程多了内存不够,照样 OutOfMemory。

3.2 端口冲突时如何改 port 与 redirectPort

8080 端口被占是 Windows 上最高频的启动失败原因之一。IIS、其他 Web 服务、甚至某些开发工具的调试端口都可能占着 8080。改端口时只改Connector的port属性是不够的,redirectPort也要同步想清楚。

<Connector port="8081" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443"/>

redirectPort的作用是:当请求是 HTTPS 且当前 Connector 只开 HTTP 时,Tomcat 把请求重定向到8443这个 HTTPS 端口。如果你只改了port而没配置对应的 HTTPS Connector,那么触发重定向时会连接失败。所以当你只需要 HTTP 时,redirectPort保持默认没关系;但千万别把这个值改成和port一样,否则会形成重定向死循环。

另一个容易被忽略的端口是Server标签上的port="8005",这个端口监听的是 SHUTDOWN 命令,shutdown.bat就是连这个端口发关闭指令。有人为了安全会改掉这个端口,改完后必须同步改shutdown.bat里的-Djava.security.manager参数吗?不用,直接改server.xml的port和shutdown字符串即可,但要注意:改完变成8006你自己得记清楚,否则下次用默认shutdown.bat关不掉服务,误以为 Tomcat 卡死了。

3.3 URIEncoding 与 useBodyEncodingForURI:中文参数的编码补丁

Tomcat 8 之后URIEncoding默认就是 UTF-8,所以纯 GET 请求的中文参数在 9.0.45 上乱码的概率已经比以前低很多。但乱码问题没有彻底消失,主要出在两类场景:一类是客户端没有按 UTF-8 发送请求,比如老系统里用 GBK 编码拼 URL;另一类是 POST 请求的 body 编码没被正确解析。

常见的加固写法是在 Connector 上显式声明编码,不要依赖默认值,因为默认行为在不同版本间可能变化,写清楚至少让后来者一眼看见你的意图。

<Connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8" useBodyEncodingForURI="true" connectionTimeout="20000" redirectPort="8443"/>

关键是useBodyEncodingForURI这个参数,它的语义是:当请求带 body 时,URL 里的查询参数也沿用 body 里声明的编码。如果你把URIEncoding设为 UTF-8,又开着这个参数,那么客户端如果以application/x-www-form-urlencoded; charset=GBK提交 body,URI 参数也会按 GBK 解析。一般来说,内部系统统一 UTF-8 时开它是省心的;对接的外部系统编码不统一时,关掉它反而更可控,让 URI 永远按 UTF-8 走。

3.4 Host 与 appBase:多应用隔离的部署边界

默认配置里只有一个Host,名字是localhost,它对应webapps目录。appBase告诉容器去哪里找应用,unpackWARs控制 war 包是否自动解压,autoDeploy控制是否动态扫描新部署的应用。

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> </Host>

autoDeploy="true"是开发阶段的好帮手,war 丢进webapps后十几秒内会被自动识别;但要小心:它同时也会持续监控解压目录里的文件变化,如果某个 class 文件被 IDE 触达,Tomcat 可能会自己触发热加载。生产环境我会把autoDeploy改成false,避免 app 目录下的静态资源被误碰导致无谓重启。

一台机器上想跑多个逻辑隔离的应用,不一定要建多个Host,大多数时候靠webapps下的不同目录名就够了。每个目录名对应一个 context path,比如webapps/order会映射到http://localhost:8080/order/。只有当你要给不同域名配不同根路径时,才需要新增Host标签,那个场景相对少见,新手阶段不必强上。

4. 把 war 包部署到 9.0.45:自动部署与外部 docBase 两种方式

4.1 方式一:丢进 webapps 的自动部署

最简单的部署方式就是把 war 包复制到webapps目录下,什么都不用改,Tomcat 会自动解压并发布。假设你把一个order-system.war丢进去,等待十几秒后,webapps下会出现一个同名目录order-system,这时访问路径就是http://localhost:8080/order-system/。

copy D:\packages\order-system.war D:\tools\apache-tomcat-9.0.45\webapps\

注意几个细节。第一,war 包的文件名就是应用的 context path,所以文件名里不要带中文、空格和特殊符号,order_system_v2.0.war这种名字会解析出order_system_v2.0路径,看起来很丑而且容易拼错。第二,如果你想让它作为根路径应用访问,也就是不带/order-system前缀直接http://localhost:8080/,那就把 war 改名为ROOT.war再丢进去。第三,同名目录已存在时,Tomcat 会先删旧目录再解压新的,但如果 Windows 上有进程占用了解压目录里的文件,删除会失败,这时要先停 Tomcat。

4.2 方式二:conf/Catalina/localhost 下的外部目录部署

有些人不想把 war 包和 Tomcat 装在同一块磁盘上,比如应用在 D 盘、Tomcat 在 C 盘;或者希望应用的目录结构完全独立,不混进webapps。这时用外部 docBase 的方式更合适。做法是在conf/Catalina/localhost/下新建一个 xml 文件,文件名就是你的应用路径,我一般会在部署某个模拟项目X时同时配合一个context配置:

<Context docBase="D:\apps\order-system\order-system.war" reloadable="true"/>

文件名起成order.xml,那访问路径就是http://localhost:8080/order/。docBase指向 war 包或解压目录的绝对路径,reloadable="true"表示 class 文件变化时自动重载,开发阶段开着;生产环境建议设成false。

这种方式的优势是应用和容器完全分离,升级 Tomcat 时不用把应用从webapps里拷出来再拷回去。但代价是失去了自动部署的直观性——你丢 war 到 webapps 至少能看见目录变化,外部部署则改完 xml 后要确认 Tomcat 确实读到了配置。我一般用http://localhost:8080/order/访问一次,能起来就说明Context生效了。

4.3 tomcat-users.xml:给 manager 控制台开一个最小权限账号

webapps目录下有一个manager应用,是 Tomcat 自带的图形化管理控制台,可以在网页上部署和卸载 war 包。默认它是完全锁死的,访问会弹 403,因为没有配置任何用户。

打开conf/tomcat-users.xml,在<tomcat-users>标签里加角色和账号:

<role rolename="manager-gui"/> <role rolename="manager-script"/> <user username="deployer" password="change-me" roles="manager-gui,manager-script"/>

manager-gui对应网页操作界面,manager-script对应命令行或脚本接口。如果只是人手动去网页上点一点,给manager-gui就够了;如果还想用 curl 脚本远程部署 war,才需要manager-script。两者的区别在于权限边界,manager-gui不能随便接受外部脚本调用,manager-script则没有图形界面。

改完要重启 Tomcat 才生效。然后访问http://localhost:8080/manager/html,输入账号密码就能看到应用列表。每次给这个账号设密码时我都建议用独立的强密码,因为 manager 控制台等于 Tomcat 的后门,外网环境如果暴露了 8080 端口,这个账号被爆破的后果比普通应用接口泄露严重得多。

5. Windows 上部署 Tomcat 9.0.45 的 5 个高频踩坑与排查

5.1 双击 startup.bat 窗口闪退,看不到任何报错

这是 Windows 新手最绝望的场景:双击startup.bat,一个黑窗口闪了一下就没了,Tomcat 没起来,也不知道为什么。闪退的根本原因是启动脚本执行失败,而失败原因被窗口关闭吞掉了。

解决方法不是去改脚本,而是绕开脚本用前台模式。打开 cmd,进入bin目录,执行catalina.bat run,所有错误信息都会留在当前窗口里。绝大多数情况的根因是JAVA_HOME没配好,或者机器上有多个 JDK 导致版本不匹配。

还有一个隐蔽原因:startup.bat调用catalina.bat时会把CATALINA_HOME作为传入参数,如果你在环境变量里配了CATALINA_HOME且指向了不存在的路径,脚本会直接退出。排查时先echo %CATALINA_HOME%,确认输出的是一个真实存在的目录。

5.2 控制台日志中文全是乱码,看得人血压升高

Tomcat 9 的日志默认用 UTF-8 编码,而 Windows 控制台默认用 GBK 解码,两边对不上,中文注释和错误信息就变成了「锟斤拷」「烫烫烫」这类经典乱码。这不是程序出错,是显示层的编码不一致。

解决方法是只改控制台输出编码,不动文件编码。编辑conf/logging.properties,找到下面这行:

java.util.logging.ConsoleHandler.encoding = UTF-8

把它改成:

java.util.logging.ConsoleHandler.encoding = GBK

注意不要顺手改FileHandler.encoding,那个控制的是日志文件落盘编码,保持 UTF-8 反而更利于后续用日志平台采集。这个问题只影响 Windows 控制台展示,改了之后重启 Tomcat,中文就正常了。

5.3 请求参数到后端变成问号,前端显示没问题

页面里提交的中文正常,但后端request.getParameter拿到的是???或者一串乱码。这个问题的定位顺序很关键:先明确是 GET 还是 POST。

GET 请求的参数在 URL 里,Tomcat 9 默认按 UTF-8 解析 URL,如果你确认浏览器地址栏里显示的中文是正常的,那问题大概率出在中间某一层代理或负载均衡把 URL 重新编码了。POST 请求的参数在 body 里,Tomcat 解析 body 编码的依据是请求头里的Content-Type是否带charset,不带就按 ISO-8859-1 处理,中文必乱。

解决分两步。第一步在 Connector 上显式声明URIEncoding="UTF-8";第二步在后端代码里保证读取参数前先执行request.setCharacterEncoding("UTF-8"),如果用的是框架,通常一个 CharacterEncodingFilter 就能统一处理。核心教训是:Tomcat 只能按它收到的编码去解析,客户端发的是 GBK,你服务端怎么设 UTF-8 都没用。

5.4 新 war 丢进 webapps 半天没反应,autoDeploy 像没开一样

autoDeploy默认是开的,但 Windows 上有两类情况会让它失效。第一类是 war 包文件名有问题,比如带中文名或版本号中带空格,Tomcat 解析 context path 失败后不会报 SEVERE,只是在日志里留一行 WARNING,很容易被忽略。第二类是文件锁,旧版本的应用还在运行,war 包里的 jar 被进程占用,Tomcat 解压新 war 时删除旧目录失败。

遇到这种情况,我的排查习惯是:先停 Tomcat,手动删除webapps下对应的旧目录和work\Catalina\localhost下同名目录,再启动 Tomcat 把 war 重新丢进去。如果还不行,打开logs/catalina.<日期>.log,搜索你的 war 包名,看有没有解压失败或Unable to delete的记录。Windows 上杀毒软件也可能拦截 war 写入,把 Tomcat 目录加白名单能减少莫名其妙的部署失败。

5.5 运行几天后内存溢出,JVM 参数改了没效果

Tomcat 默认的 JVM 堆内存上限是物理内存的 1/4,对于业务量不大的内部系统可能够用,但 JSP 应用如果频繁编译、Session 里塞大对象,几天后就会在catalina.<日期>.log里看到java.lang.OutOfMemoryError: Java heap space。

很多人会去改catalina.bat里的JAVA_OPTS,改完发现没生效,原因在 Tomcat 启动的优先级机制:catalina.bat会先找bin\setenv.bat,如果这个文件存在,里面的CATALINA_OPTS会覆盖或追加到默认 JVM 参数上。正确做法是在bin目录下新建一个setenv.bat:

set CATALINA_OPTS=%CATALINA_OPTS% -Xms512m -Xmx1024m -XX:+UseG1GC

-Xms是初始堆大小,-Xmx是最大堆大小,两者设成一样可以避免 JVM 动态调整堆大小带来的性能抖动,但会多占物理内存。-XX:+UseG1GC是 JDK 9 之后推荐的垃圾收集器,对 Tomcat 这种中等对象压力的场景比默认的 CMS 更稳。改完重启 Tomcat,用jps -lv查看进程的启动参数,能直接确认-Xmx1024m是否真的加上了。

6. 把 Tomcat 9.0.45 调成 Windows 常驻服务:JVM 内存参数与控制台管理

前面几章解决了「能跑」和「跑得稳」,最后一节解决「人走了它还在」。在 Windows 服务器上部署 Tomcat,正确归宿是注册成系统服务,这样开机自动启动、崩溃自动拉起、用户注销不掉线。注册工具不需要额外装任何东西,zip 包里的service.bat和tomcat9w.exe就是干这个的,前提是JAVA_HOME和CATALINA_HOME都已经配好。

以管理员身份打开 cmd,执行:

cd /d C:\tools\apache-tomcat-9.0.45\bin service.bat install Tomcat9

提示The service 'Tomcat9' has been installed后,去服务管理器(services.msc)里就能看到 Tomcat9 服务,先手动启动一次确认没问题,再把启动类型改成自动。这里有个关键细节:服务模式下 JVM 参数不再从setenv.bat读取,而是写入 Windows 注册表的服务配置项里,所以你在catalina.bat里调的堆内存对服务模式无效。

调整服务模式下的内存参数,用bin目录下的tomcat9w.exe。运行它,切到 Java 选项卡,在Initial memory pool和Maximum memory pool里填 512 和 1024,单位是 MB,填完点确定,重启服务生效。

我个人的习惯是每次配完服务后不急着走:先catalina.bat run前台启动一次,确认应用本身没报错;再停掉前台进程,用服务方式启动,打开logs\catalina.<日期>.log看前 20 行,确认没有SEVERE和内存参数异常。这个「前台验证 + 服务跑日志」的流程帮我挡掉了不少自以为配好了、重启后却起不来的尴尬。Tomcat 9.0.45 在 Windows x64 上不复杂,但每一步都值得亲眼看一眼日志再往下走,希望帮到你。

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

RELATED READING

延伸阅读

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