☰
Linux下Tomcat 8.5.35安装配置与部署实战:从tar.gz到systemd自启动
2026/9/26 4:43:39 网站建设 项目流程

简介:一份Linux环境下的Tomcat 8.5.35完整发行包,面向需要在Linux服务器上部署、运行Java Web应用的开发与运维人员。该版本基于Apache Tomcat,支持Java EE 8规范中的JSP 2.3、EL 3.0、WebSocket等特性,并修复了若干安全漏洞,适合从开发到生产环境直接采用。资源包含645个文件,压缩后约9.2MB,核心文件类型涵盖jar库、class类文件、java/jsp源码、html页面、xml配置文件以及sh启动脚本等。其中jar库支撑Servlet容器运行,class/java/jsp构成可运行模块与示例,xml用于配置端口和访问规则,sh脚本用于启停管理。目录结构遵循标准Tomcat布局,bin、conf、lib、logs、webapps、work等一应俱全,便于快速上手和排查问题。目前已有554人学习或下载。可直接通过设置CATALINA_HOME启动运行;也可通过修改server.xml调整访问控制、启用HTTPS或优化连接器参数。对于需要掌握Tomcat内部结构或搭建Java Web服务的人员,这份资源提供了可直接对照学习的标杆环境。该版本在稳定性与兼容性上表现良好,适合个人学习与团队生产使用。

1. 一个 Linux 版 Tomcat 8.5.35 的 tar.gz 包:拆开之前先想清楚三件事

手上一台干净的 Linux 服务器,拿到apache-tomcat-8.5.35.tar.gz,你首先要确认的不是解压命令,而是三件事:JDK 装没装、版本对不对、这个包打算放哪个目录。Tomcat 8.5.35 是一个很典型的过渡版本——它既能吃 JDK 8 的老项目,也开了支持 Java 9+ 的口子,但真正生产环境里跑得最稳的依然是 JDK 8。整个安装及配置教程走到最后,你会发现花时间最多的不是解压,而是 server.xml、catalina.sh 和日志里的那几行报错。

这篇笔记讲的就是一条可复现的路径:解压 tar.gz,配 JDK 环境,改连接器与 JVM 参数,部署前后端分离或传统 WAR 包,最后做 systemd 自启动。顺带把 8.5.35 这个版本特有一些坑标出来,比如 AJP 连接器行为差异、JVM 参数踩到高版本 JDK 的不兼容、以及日志里最常见的那几个“假死真因”。

2. 解压安装前的准备:JDK 版本、tar 命令与目录规划

2.1 先确认 Linux 发行版和 JDK 版本:8.5.35 对 Java 8 的兼容性

Tomcat 8.5.35 是 2019 年初发布的版本,官方要求的最低 Java 版本是 JDK 7,但实际部署中绝大多数团队都在 JDK 8 上跑它。你如果直接上 JDK 11,不是不能跑,而是 8.5.35 里某些反射和安全管理器相关的代码在更早版本上做过适配,跑在 JDK 11 上偶尔会有类加载时序问题。经验做法是:部署 8.5.35 就用 Java 8,别给它配 11 或 17。

# 查看当前 Java 版本 java -version # 如果没有输出,先确认是否有 JDK 安装包 ls /usr/lib/jvm/

如果java -version报错,说明环境里根本没有 JDK。常见做法是从发行版仓库装 OpenJDK 8。CentOS/RHEL 系执行这条命令,安装后/usr/lib/jvm/java-1.8.0-openjdk就是 JDK 根目录。

yum install -y java-1.8.0-openjdk-devel

这里注意后缀必须是-devel,只装java-1.8.0-openjdk是没有javac的。Tomcat 本身不编译 JSP 时可能感知不到,但一旦 JSP 页面首次访问需要编译成 class,缺 JDK 会直接抛Unable to load class或JasperException。

2.2 解压 tar.gz 到指定目录:用 tar 命令做最小安装

以标题中的安装包命名规律,解压命令是标准的tar -zxvf。Linux 下对 tar.gz 文件最常用的解压参数是-zxvf,四个字母分别表示:解压、gzip 解压、显示过程、指定文件名。

# 创建安装目录并解压 mkdir -p /opt/tomcat tar -zxvf apache-tomcat-8.5.35.tar.gz -C /opt/tomcat # 解压后会生成 apache-tomcat-8.5.35 目录 ls -la /opt/tomcat/

-C指定了解压目标目录,这是很多新手漏掉的参数。如果不写,tar 会把目录解到当前所在路径。解压完成后,我习惯顺手建立一个不带版本号的软链,后面 systemd 脚本和日志路径都引用软链,升级时只需要改链,不用改配置。

ln -s /opt/tomcat/apache-tomcat-8.5.35 /opt/tomcat/tomcat8

解压后你应该看到这些目录:bin(启动脚本)、conf(配置文件)、lib(运行时依赖 jar)、logs(日志输出)、webapps(部署根目录)、work(JSP 编译缓存)。这里面最容易被忽略的是work目录——你改了一个 JSP 但页面不变时,先清work再重启,这属于 Tomcat 的常见脾气。

2.3 首次启动前检查:JAVA_HOME、目录权限和 catalina.out 的定位

解压只是把文件摆到位,接下来三个检查能避免 90% 的启动翻车。

先配JAVA_HOME。Tomcat 的启动脚本catalina.sh会优先读取环境变量JAVA_HOME,其次用JAVA_HOME定位java和jdb。把下面两行写进/etc/profile或/root/.bashrc:

export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export CATALINA_HOME=/opt/tomcat/tomcat8 export PATH=$PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin

然后是目录权限。Tomcat 在 Linux 上不会用 root 运行,生产上更常见的做法是单独建一个tomcat系统用户,把logs、temp、work三个目录的写权限交给它。但如果你只是本地验证,先用当前用户跑通再说,权限问题后面统一收口。

最后确认日志位置。第一次启动不要用startup.sh,用前台模式:

/opt/tomcat/tomcat8/bin/catalina.sh run

这样日志直接打到终端,启动异常当场可见。等确认正常了,再用startup.sh放到后台。catalina.out是 stdout 重定向文件,logs/catalina.out会记录所有控制台输出,之后排查内存、乱码、部署异常,第一手证据都在这里。

3. 核心配置:server.xml、catalina.sh 与 JVM 参数,改动前先备份

3.1 server.xml 必改的三个点:端口、Host 与 Connector 参数

conf/server.xml是 Tomcat 的骨架。8.5.35 在默认配置下有五组端口:8005(shutdown)、8080(HTTP)、8009(AJP)、8443(HTTPS)。多数情况你得动的是 8080 和 8009 这一组。

常用做法是给生产环境换掉默认端口,避免和同机其他服务撞车。下面这段是生产上最常见的 Connector 配置,我把改动集中在port、protocol和URIEncoding三处:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" useBodyEncodingForURI="true" maxThreads="400" minSpareThreads="50" acceptCount="200" maxConnections="10000" compression="on" compressionMinSize="2048"/>

逐项解释这些参数的用途:maxThreads控制最大工作线程数,默认 200 对于接口型应用偏小,调到 400 是常见做法;minSpareThreads是空闲时保持的线程数,设太小会导致瞬时并发时线程创建延迟;acceptCount是等待队列长度,超过maxThreads + acceptCount的请求才会被拒绝。compression打开后能明显压小页面传输体积,但对二进制接口没什么意义。

改动之前务必备份原文件,这是我给自己立的规矩,改完 server.xml 再改回来是经常有的事。备份就一条命令:

cp /opt/tomcat/tomcat8/conf/server.xml \ /opt/tomcat/tomcat8/conf/server.xml.bak.$(date +%Y%m%d)

改完配置别急着重启,先做语法校验:bin/catalina.sh configtest会检查 server.xml 的结构,输出Server.xml configuration is OK再重启,能省不少白等的时间。

3.2 给 catalina.sh 加 JAVA_OPTS:堆内存、Metaspace 与编码问题

Tomcat 默认的 JVM 参数极其保守:堆内存只有物理内存的 1/4 左右上限,PermGen/Metaspace 也按默认值走。所以你会在日志里看到两种典型挂法:堆内存溢出(java.lang.OutOfMemoryError: Java heap space)和类元数据溢出(Metaspace溢出)。8.5.35 加上 JDK 8,Metaspace 是重点。

我一般直接在catalina.sh里追加一段JAVA_OPTS,因为这台机器只跑这一套 Tomcat,写在全局环境变量里会污染其他应用。

# 在 catalina.sh 的 JAVA_OPTS 赋值处追加 JAVA_OPTS="$JAVA_OPTS \ -Xms1g \ -Xmx2g \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=512m \ -Dfile.encoding=UTF-8 \ -Djava.awt.headless=true"

参数说明:-Xms1g和-Xmx2g表示堆初始 1G、最大 2G,生产上我习惯把两者设成不同值,因为 Tomcat 启动时的类加载并不需要一次性占满堆,给初始化留点余地;-XX:MetaspaceSize是触发类加载回收的阈值,设太小会导致频繁 Full GC;-Dfile.encoding=UTF-8是应对 Linux 系统默认编码不是 UTF-8 时的兜底方案,文件读写、日志输出、网络传输都受它影响。java.awt.headless=true是为了防止图形环境缺失时报 NoClassDefFoundError。

改完 JAVA_OPTS 后重启,看catalina.out里是否出现你设置的值。可以用jps加jinfo验证:

jps -l | grep Bootstrap jinfo -flags <进程号>

jinfo如果显示 Available 的-Xmx已是你设置的值,说明参数生效。

3.3 URIEncoding 与字符集乱码:连接器 URL 编码的兜底方案

Tomcat 8.5.x 和之前的 7.x 有个行为差异:从 8.0 起,默认 URIEncoding 已经是 UTF-8,所以理论上 GET 请求里的中文参数不会乱码。但现实中乱码依然高频出现,原因多为两类:一是页面本身没声明 UTF-8,提交的请求编码乱掉;二是后端代码按 GBK 读取参数。

URIEncoding解决的是 URL 解码侧的问题。就算 Tomcat 默认是 UTF-8,我在 server.xml 里仍然显式加上它——这个习惯救过多次命,特别是当 web.xml 里的编码 filter 被移除或顺序不对时。加上之后,配合前端的<meta charset="UTF-8">与后端的request.setCharacterEncoding("UTF-8"),乱码排查范围能直接缩小到代码提交侧。

我自己常用的排查路径是:

# 看访问日志里的原始请求,确认是 GET 还是 POST 乱码 tail -f /opt/tomcat/tomcat8/logs/localhost_access_log.txt

访问日志里如果原始 URL 已经带%E4%B8%AD%E6%96%87这类 UTF-8 编码,说明浏览器侧没问题,问题在后端读取或数据库连接串;如果原始 URL 里是 GBK 编码的%D6%D0%CE%C4,那就要在浏览器或前端框架层处理。这一步让你精确知道该往哪追,不靠玄学。

4. 部署 WAR 包与日志分析:前后端分离项目的两条常规路径

4.1 用 webapps 目录直接部署 WAR:热替换与解压目录

Tomcat 的传统部署方式就是把 WAR 包扔进webapps目录,重启后自动解压并加载。8.5.35 默认unpackWARs="true",它会先解压到同名目录,再从这个目录加载应用。所以你会看到webapps/your-app/和your-app.war同时存在。

# 把构建产物放到 webapps 下 cp your-app.war /opt/tomcat/tomcat8/webapps/ # 重启让 Tomcat 重新加载 /opt/tomcat/tomcat8/bin/catalina.sh stop /opt/tomcat/tomcat8/bin/catalina.sh start

这里有个 8.5 特有的行为值得注意:autoDeploy默认是true。也就是说,当你再拷贝一个新your-app.war覆盖旧包,Tomcat 会检测到 WAR 文件的修改时间变化,自动解压并重新加载应用,不需要重启。这个行为在开发环境很爽,在生产线是隐患——你在不知情时覆盖了包,服务就悄悄重启了,连接中的用户直接被断掉。

所以生产环境的 Host 标签我一般显式关闭它,只保留手动重启:

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

如果保留autoDeploy="true",就必须接受“包一换、应用重载”这个事实。应对方法是部署窗口期做替换,并且替换后立刻检查日志里的Deployment of web application archive记录。

4.2 用软链映射外部目录:不在 webapps 下放包的另一种做法

对于前端把构建产物打成静态目录、后端只出接口的项目,很多人不愿意把前端目录塞进 webapps。Tomcat 支持在conf/server.xml的 Host 节点下加<Context>映射,把来自某个路径的请求指向一个外部目录。这在 Linux 部署前后端分离项目时非常常用。

<Context docBase="/data/static/webroot" path="/static" reloadable="true"/>

docBase是外部目录的绝对路径,path是访问该目录的 URL 前缀。上述配置意味着http://ip:8080/static/index.html会直接读取/data/static/webroot/index.html。这样的好处是:前端构建完直接 rsync 到/data/static/webroot,不需要打包成 war,也不用重启 Tomcat。

但这个做法的坑是docBase必须存在且可读,Tomcat 启动时如果发现 docBase 不存在或没有权限,会报Failed to initialize component [Container]一类错误导致整个 Host 起不来。所以要养成先建目录后加配置的习惯。

4.3 catalina.out 与 localhost.log:启动异常时先看哪份日志

Tomcat 的日志体系在 8.5.35 里是分流的,每种日志有独立文件:

  • catalina.out:所有 JVM 和容器级的 stdout/stderr 输出
  • localhost.log:Web 应用自身标准输出和未捕获异常
  • localhost_access_log.txt:HTTP 访问日志,记录每次请求的 IP、方法、路径、状态码和耗时
  • manager.log/host-manager.log:管理后台相关

启动异常时先看catalina.out的最后 50 行,基本能定位到是端口占用、类加载失败还是配置语法错误。应用运行时偶发错误看localhost.log,比如Servlet.service() for servlet ... threw exception这种,异常栈会完整打在这里。按状态码排查访问问题看localhost_access_log.txt。

我排查 Tomcat 问题的固定套路是:

# 1. 看整体启动是否成功 tail -100 /opt/tomcat/tomcat8/logs/catalina.out # 2. 看具体应用的异常栈 tail -100 /opt/tomcat/tomcat8/logs/localhost.log # 3. 看某时间段内的访问状态,确认是不是被大量请求拖垮 grep "$(date +%d/%b/%Y:%H)" /opt/tomcat/tomcat8/logs/localhost_access_log.txt | awk '{print $9}' | sort | uniq -c

最后这条命令把某一小时内所有响应状态码聚合,如果500或504数量激增,那问题通常在后端接口而不是 Tomcat 本身。这个习惯能省掉大把抓瞎时间。

5. 五个高频踩坑记录:从启动失败到内存溢出,现象、原因、解法

5.1 现象:启动后进程秒退,catalina.out 没有任何异常堆栈

用startup.sh启动后ps -ef | grep tomcat找不到进程,但打开catalina.out又只有几行 INFO 日志,没有报错。这种“安静地死掉”特别容易让人一头雾水。

原因通常是两个:一是JAVA_HOME环境变量没有在启动脚本所在 shell 中生效;二是catalina.sh执行时找不到java命令,从而静默退出。另一个隐藏原因是内存不足,JVM 启动参数-Xmx分配过大而物理机内存不够,OOM Killer 直接把进程杀了,这种情况下dmesg里能看到痕迹。

解决方式是先在前台跑catalina.sh run,让日志直接输出到终端,没有堆栈也不怕。如果还是只停几行,就检查环境变量:

echo $JAVA_HOME ulimit -a | grep virtual

ulimit -a中的virtual memory如果是unlimited就没问题,如果有限制,可能 JVM 都起不来。8.5.35 在容器环境里经常遇到这类 cgroup 限制问题,低层根因是系统限制和 JVM 探测内存逻辑冲突。

5.2 现象:Tomcat 能启动,但访问页面全部报 404

这个现象在 8.5.35 上有一个非常经典的误导性原因:你访问的路径匹配不上 Host 的appBase。如果 WAR 包名是demo.war,那么访问路径应该是http://ip:8080/demo/,而不是http://ip:8080/。很多人以为把包扔进 webapps 就能根路径访问,结果一直 404。

另外一类原因是webapps目录下存在同名但残缺的目录,比如上一次部署中断留下的demo/目录是空的,Tomcat 解压时发现目录已存在但内容不完整,不会覆盖,直接按目录加载,加载出来自然是空的。

解决方式是清理后重放:

# 删除残缺的部署目录 rm -rf /opt/tomcat/tomcat8/webapps/demo # 重新放包 cp demo.war /opt/tomcat/tomcat8/webapps/

之后确认webapps/demo目录里确实生成了WEB-INF和页面文件,再访问。Tomcat 部署目录和 WAR 包不同步是 404 的最大来源。

5.3 现象:页面中文全部变成问号或乱码,数据库里的中文也是乱码

乱码这个问题的定位顺序很重要,我按从外到内排个优先级:先看页面响应头是否声明了 UTF-8,再看 Tomcat 的URIEncoding,再看数据库连接串是否带了characterEncoding=utf8。三者缺一不可。

8.5.35 的Connector上URIEncoding="UTF-8"只解决 GET 的 URL 参数,不解决 POST 表单。POST 乱码的根源在 servlet 容器解析请求体时的默认编码,Java Servlet 规范默认是 ISO-8859-1。所以如果你没有在代码里调request.setCharacterEncoding("UTF-8"),POST 必乱。

# 看访问日志里的原始请求,确认浏览器和服务器之间编码是否一致 grep "POST" /opt/tomcat/tomcat8/logs/localhost_access_log.txt | tail -10

如果原始请求里的中文是%E4%B8%AD这类 UTF-8 形态,说明浏览器没问题,后端读取方式错了。解决就是设置一个编码过滤器,把CharacterEncodingFilter重定向到容器级。用 Spring Boot 时server.servlet.encoding.force=true是快捷键,但 8.5.35 不挂 Spring Boot 时就得自己写 Filter 或调 Catalina 级参数。

5.4 现象:应用接口调用偶发失败,日志出现Connection reset by peer或SocketException: Unexpected end of file

这个错误在网上被讨论很多,但实际原因比大多数人想的普通:一是 KeepAlive 超时和 Nginx 代理超时不匹配,浏览器和 Tomcat 保持长连接,但代理侧proxy_read_timeout设置太短,代理断开连接而 Tomcat 不知道,下次复用连接就报错。

另一种就是 CPU 或线程池被打满,Tomcat 处理不过来时会对积压连接做丢弃,表现为reset。8.5.35 的 NIO 连接器在maxThreads=200默认值下尤其容易触发,接口里如果有慢 SQL 或同步 RPC,线程池很容易被打满。

解决方向很简单,看catalina.out有没有All threads are busy相关日志。有的话提升线程池并压测验证,没有的话检查负载均衡与 Tomcat 的超时一致性:

# 所有线程池状态和当前线程数,立即确认是否打满 jstack <pid> | grep "http-nio" | wc -l

jstack输出当前所有存活的 Java 线程名,数一数http-nio-8080-exec-开头的线程数量,接近maxThreads就说明池子已满。这时候不是盲目加线程数,而是去查哪一个接口占用了最长时长的调用。

5.5 现象:JVM 内存溢出,日志里频繁出现OutOfMemoryError: Metaspace

前面讲了改JAVA_OPTS时的-XX:MetaspaceSize与-XX:MaxMetaspaceSize,这里出现的是真实踩过的坑:Tomcat 8.5 在 JDK 8 下,如果部署了多个应用,每个应用自带一套第三方依赖,Metaspace 会快速涨。默认 20M 左右的上限对稍微复杂的系统都不够用。

解决分两步走。第一步改配置:

JAVA_OPTS="$JAVA_OPTS -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"

第二步找根因:Metaspace 涨得快的项目,基本是类加载器泄漏,常见是重复部署 WAR 包且依赖了ThreadLocal、单例持有 ClassLoader 引用。所以不要只改配置,还要看每次 reload 之后 Metaspace 是否回落。不回落的,说明有类加载器被静态引用持有了,改配置只是延缓死亡。

我验证 Metaspace 是否正常回落的命令是:

# 连续观察 3 次 GC 后的 Metaspace 占用 jstat -gcmetacapacity <pid> 1000 5

输出里MCMN(最小容量)和MCMX(最大容量)如果持续上涨并贴近MaxMetaspaceSize,就可以确定类加载器泄漏,这时候得去查代码里谁引用了 WebappClassLoader。

6. 生产化收尾:用 systemd 托管 Tomcat 自启动,并验证开机拉起

标题这个 tar.gz 包解压出来的 Tomcat 默认没有任何自启动机制。如果你重启一次 Linux 服务器,还得手动敲startup.sh,这在生产环境是不能接受的。8.5.35 对应最常见的托管方式就是 systemd 服务。先创建一个单元文件tomcat.service,将进程以前台方式托管:

[Unit] Description=Apache Tomcat 8.5.35 Web Application Container After=network.target [Service] Type=simple User=tomcat Group=tomcat EnvironmentFile=/opt/tomcat/tomcat8/conf/tomcat.env ExecStart=/opt/tomcat/tomcat8/bin/catalina.sh run ExecStop=/opt/tomcat/tomcat8/bin/shutdown.sh 60 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

核心参数说明:Type=simple表示catalina.sh run是主进程,systemd 直接跟踪它,而不是用startup.sh那种 fork 方式去追子进程;EnvironmentFile加载 JDK 和内存参数环境变量,避免把环境变量写死在全局文件中;ExecStop倒计时 60 秒,给 Tomcat 足够时间完成优雅停止。如果你只用 root 跑,把User和Group两行去掉即可,但生产上强烈不建议 root 跑 Tomcat。

启用并验证:

cp tomcat.service /etc/systemd/system/ systemctl daemon-reload systemctl enable tomcat systemctl start tomcat systemctl status tomcat

systemctl status输出里要能看到Active: active (running)才算成功。最后一步我习惯直接 reboot 一次服务器,等机器起来后不手动做任何操作,直接访问端口验证服务确实被拉起。这一步不能省,因为环境变量在重启后的加载时机、网络依赖顺序等问题只会在真实启动流程里暴露。

检查访问:

curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8080/

返回 200 且能看到 Tomcat 欢迎页代码时,这个 tar.gz 包就算正式投产了。

我自己的收尾习惯还有一条:把这次部署用到的所有自定义配置——server.xml 改动、JAVA_OPTS、systemd 单元文件——放到一个部署目录统一留存,下次换机器时直接拿来 diff,不靠记忆还原配置。这套从解压到自启动的流程跑过几遍之后,你会发现 Tomcat 8.5.35 真正值钱的地方不在于它有多少黑科技,而在于你能把每一步的异常都看得明明白白。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询