☰
Tomcat生产环境部署全攻略:从JDK安装到JVM调优与排错
2026/9/28 6:02:27 网站建设 项目流程

部署Tomcat这事儿,说难不难,说简单也有一堆坑等着你。从下载解压到跑起来一个能用的Web服务,中间隔着JDK版本匹配、端口配置、JVM参数、自启动脚本、日志切割、前后端分离路由……任何一个环节偷懒,后面上线都可能给你颜色看。这篇东西不是照抄官方文档的翻译稿,而是把我在Linux上从零部署Tomcat、给生产环境做配置调优、踩完各种坑之后沉淀下来的完整流程和心得写出来,从安装到运维再到排错,一条线捋清楚。适合刚接触Java Web部署的开发者、需要自己维护服务器的后端工程师,以及被分配了“把项目跑起来”任务的运维新人——看完至少能少走三天弯路。

1. 内容整体设计与思路拆解

1.1 Tomcat到底是什么,为什么我们需要它

Tomcat本质上是Apache Software Foundation维护的一个开源Servlet容器,负责运行Java Web应用,把HTTP请求转换成Java对象交给你的业务代码处理,再把结果包装成HTTP响应返回给客户端。说得生活化一点:Tomcat像一家餐厅的服务员团队,客人的点餐(HTTP请求)由服务员接单,转交给后厨(你的Servlet/Spring MVC代码),后厨做好菜(业务数据),服务员再端上桌(HTTP响应)。没有这个服务员团队,你写的菜品再好也送不到客人面前。

很多初学者容易混淆的概念是:Tomcat既是Web服务器,也是Servlet容器。Web服务器意味着它能处理静态资源(HTML、CSS、JS、图片),Servlet容器意味着它能运行Java Servlet/JSP。它不像Nginx那样以高性能处理静态文件和反向代理见长,但胜在Java生态的原生支持,Spring Boot内置的Web容器默认就包含Tomcat,这也是为什么前后端分离架构里,Nginx负责静态资源并反向代理到Tomcat后端,两者分工配合的搭配非常常见。

1.2 部署方案选型:为什么选择“传统Tomcat”而不是“全家桶”

现在新项目大多直接Spring Boot内置Tomcat,一个java -jar就完事了,那为什么还要专门学传统Tomcat部署?因为生产环境里还有大量传统Java Web应用,或者说你工作中必然要接手老项目——Struts、Spring MVC、SSH/SSM打成的WAR包,这些都是部署在独立Tomcat上的。另外,独立部署Tomcat也方便做多应用隔离、独立JVM参数调优、与Nginx配合做负载均衡。

我这篇文章采用“Linux服务器 + 官方tar.gz包 + systemd自启动 + 独立JVM调优 + 日志切割”这套组合,也是生产环境最通用的方案。不推荐直接用apt install tomcat9这类包管理器安装,因为仓库里的版本通常滞后,而且目录结构、权限、版本都有系统包管理器的痕迹,不好控制。官方tar.gz包虽然是绿色解压版,但配合自建systemd服务,可控性是最高的。

1.3 受众定位与预期收获

这篇文章的设计思路是先建环境、再配应用、后做优化、最后排坑。读完你应当能独立完成:

  • 在Linux上安装指定版本的JDK和Tomcat并正确配置环境变量
  • 理解server.xml的核心配置节点并完成端口、线程池、虚拟主机调整
  • 部署WAR包、实现Tomcat与Nginx的前后端分离架构
  • 配置systemd自启动和JVM参数
  • 面对端口占用、乱码、404、内存溢出等问题时有明确的排查路径

2. 安装部署实操要点:从JDK到第一个Hello World

2.1 JDK版本选择与安装

Tomcat本质上是Java程序,先装JDK是硬前提。版本对应关系得先理清楚,否则Tomcat可能起不来。

Tomcat版本最低Java版本主要特性
Tomcat 8.5Java 7+老项目主流,兼容性好
Tomcat 9.0Java 8+支持Servlet 4.0,老项目新项目都能用
Tomcat 10.1Java 11+Jakarta EE 9+,包名改为jakarta.*
Tomcat 11Java 17+最新版本,新项目可选

注意:Tomcat 10及以后版本把javax.servlet迁移成了jakarta.servlet,如果你的项目代码还在用javax包名,直接跑在Tomcat 10上会报ClassNotFoundException,这是升级时最常踩的坑。

JDK安装我用的是OpenJDK。官方tar.gz解压到/usr/local/java,然后配置环境变量:

# 下载对应版本,以JDK 8为例 tar -zxf jdk-8u421-linux-x64.tar.gz -C /usr/local/java # 编辑 /etc/profile,追加 export JAVA_HOME=/usr/local/java/jdk1.8.0_421 export PATH=$JAVA_HOME/bin:$PATH export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar # 使配置生效 source /etc/profile java -version

生产环境里我习惯用一个专门账号跑Tomcat,不推荐直接用root。一是安全,二是防止某个Web应用被攻破后拿到root权限,那就全盘失守了。建账号的命令非常简单:

useradd -r -s /bin/false tomcat

-s /bin/false限制该用户不能登录Shell,纯粹作为服务运行账号。

2.2 Tomcat下载与解压安装

代码包建议从Apache官网或国内镜像站下载。官网地址是tomcat.apache.org,下载页里选对应版本的tar.gz包,不要下zip包,Linux上tar.gz更干净。

mkdir -p /opt/tomcat tar -zxf apache-tomcat-9.0.98.tar.gz -C /opt/tomcat ln -s /opt/tomcat/apache-tomcat-9.0.98 /opt/tomcat/current

做软链接current的好处是以后升级版本时,只需要解压新包然后改一下软链接,配置文件路径不变,脚本引用路径不变,平滑升级。

再把目录归属给tomcat用户:

chown -R tomcat:tomcat /opt/tomcat

2.3 环境变量CATALINA_HOME与首次启动验证

Tomcat启动依赖两个环境变量:CATALINA_HOME(安装路径)和CATALINA_BASE(实例路径)。单实例部署时两者可以指向同一路径,多实例部署时base指向各自实例目录。

在/etc/profile里追加:

export CATALINA_HOME=/opt/tomcat/current export PATH=$CATALINA_HOME/bin:$PATH

然后启动验证:

/opt/tomcat/current/bin/startup.sh

看到Tomcat started只是JVM起来了,不代表一切正常。立刻检查两件事:第一,logs/catalina.out里有没有异常堆栈;第二,浏览器访问http://服务器IP:8080,确认能打开那只经典的Tomcat猫页。

提示:如果8080端口访问不通,优先检查云安全组入方向和服务器本地防火墙。很多人部署没问题,最后是防火墙策略挡在外面。

启动方式需要区分一下:startup.sh是后台启动,会放一个daemon进程;catalina.sh run是前台运行模式,日志直接打印到控制台,适合调试时临时用。生产环境用systemd托管,就不用手动执行startup.sh了。

3. 核心配置文件server.xml深度解析与调优

3.1 三个关键端口:8005、8080、8009

打开conf/server.xml,配置主体分三层:Server、Service、Connector和Engine及Host。三个端口各司其职:

端口默认值作用注意事项
Shutdown端口8005接收关闭命令生产环境建议改掉默认值,防止外部直接关闭服务
HTTP连接器8080对外提供HTTP服务主要调优对象
AJP连接器8009与Apache HTTP Server集成用不用Apache时建议注释掉

AJP端口很多场景下是多余的,现在普遍用Nginx反代HTTP请求,根本用不到AJP。留着它反而多一个暴露面,所以我的建议是直接注释掉。

3.2 Connector参数调优:线程池、超时与编码

HTTP Connector是整个Tomcat性能的门户。先看一组生产环境常用配置:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="400" minSpareThreads="50" acceptCount="200" maxPostSize="10485760" URIEncoding="UTF-8" enableLookups="false" compression="on" compressionMinSize="2048" compressibleMimeType="text/html,text/xml,text/plain,text/css,application/json"/>

每个参数背后都有讲究:

  • maxThreads:最大工作线程数,默认200。不是越大越好,每个线程都占JVM栈空间(默认1MB),400个线程就占了400MB栈内存,还要考虑CPU核心数和数据库连接池上限。一般按每秒请求量 × 平均响应时间估算,比如QPS预期500、平均响应200ms,那么活跃线程约100个,留点余量设200即可。
  • minSpareThreads:启动时预创建的空闲线程,减少请求到来时的线程创建开销。
  • acceptCount:请求队列长度。当线程全忙时新请求进入队列等待,队列满后请求会被拒绝。值太大导致请求排队时间过长,用户体验差;值太小又容易直接拒绝,建议200~500之间。
  • maxPostSize:表单POST提交的最大字节数,默认2MB,很多文件上传功能就是被这里卡住的,需要时改大。
  • URIEncoding:统一设置UTF-8,否则带中文参数的GET请求会出现乱码。
  • enableLookups="false":关闭DNS反查,避免每次请求做域名解析拖慢响应。
  • compression:压缩静态文本资源,减少传输体积,对带宽敏感场景提升明显。

那么Tomcat会如何处理一个连接:接收请求后分配给一个空闲线程执行,线程不够就放到accept队列里排队,再不够就直接拒绝。maxThreads、acceptCount和操作系统TCP backlog共同决定了系统的抗压上限,参数不是越大越好,要和JVM堆内存、CPU负载配套调整。

3.3 Host虚拟主机配置

Engine下可以挂多个Host,实现一台机器上多个域名对应多个应用目录的虚拟主机效果。

<Host name="www.example.com" appBase="webapps" autoDeploy="true" unpackWARs="true"> <Context path="" docBase="/data/apps/example" reloadable="false"/> </Host>

几个属性要看清:

  • appBase:Web应用存放的基本目录,默认是webapps。
  • autoDeploy:是否自动部署新放入的WAR包,开发环境开着方便,生产环境建议关掉,防止误放文件触发应用重载。
  • unpackWARs:是否自动解压WAR包,开发环境开,生产环境可以关,但关掉后访问解压路径有差异,一般保持默认true。
  • <Context>的path:访问路径,空字符串代表域名根路径直接访问应用。

生产环境我更习惯把应用目录放到webapps之外,比如/data/apps/,用docBase指定。这样升级应用时不会误删Tomcat自己的目录结构,也方便给应用独立的管理区域。

3.4 多实例部署的思路

一台机器上跑多个Tomcat实例的需求很常见——不同项目需要隔离的JVM和不同端口。传统做法是复制整套Tomcat目录,占磁盘空间又乱;推荐做法是共享同一份二进制目录,为每个实例单独建conf、logs、webapps、work、temp目录,然后设置CATALINA_BASE指向实例目录。

export CATALINA_HOME=/opt/tomcat/current export CATALINA_BASE=/data/tomcat-instances/app1 /opt/tomcat/current/bin/startup.sh

修改实例目录下的conf/server.xml里的端口号,避免和别的实例冲突。这样做的好处是二进制升级一次,所有实例同时受益,维护成本直降。

4. 应用部署实操:WAR包、前后端分离与IDE开发环境

4.1 WAR包部署的几种玩法

传统Java Web应用打成WAR包后,部署方式有下面几种:

方式操作适用场景
丢进webapps目录cp app.war /opt/tomcat/current/webapps/最简单,但生产环境不推荐
Context描述符在conf/Catalina/localhost/下写一个app.xml,指定docBase应用可放任意目录,推荐
Manager管理界面浏览器登录manager页面远程部署管理多个应用时方便
IDE部署IDEA里配置Tomcat Server直接热部署开发调试阶段

上面表格之外的现代化做法是走CI/CD流水线,Maven或Jenkins打包后直接scp到服务器再调部署脚本,结合autoDeploy=false可以做到受控部署。

4.2 前后端分离项目部署架构:Nginx + Tomcat

前后端分离是当前主流,典型架构是:

  • 前端:Vue/React打包成静态资源(HTML、CSS、JS),放在Nginx
  • 后端:Spring Boot或者Spring MVC项目打成WAR包部署在Tomcat
  • 动态请求:Nginx把/api/前缀的请求反向代理到Tomcat的8080端口

Nginx配置关键部分:

server { listen 80; server_name example.com; root /data/frontend/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } }

两个容易出错的地方提醒一下:

第一,proxy_pass的路径拼接问题。location /api/配proxy_pass http://127.0.0.1:8080;(不带斜杠)时,请求/api/user/list会原样转发给后端;如果proxy_pass http://127.0.0.1:8080/;(带斜杠),会把/api/前缀去掉再转发,变成/user/list。后端接口没写/api前缀就用第一种,写了就一句带过。这个细节能排查掉一半的502/404问题。

第二,try_files $uri $uri/ /index.html;是为了解决Vue Router的history模式刷新404问题。用户直接访问/about时,Nginx找不到对应文件,回退到index.html交给前端路由处理。如果用hash模式(URL带#),不需要这段配置。

4.3 IDEA中配置Tomcat进行本地开发调试

开发环境配Tomcat,很多人卡在IDEA的配置入口。简单捋一遍:

  1. 打开Run/Debug Configurations
  2. 点+号,选择Tomcat Server下的Local
  3. Application Server右侧点Configure...,选择Tomcat安装目录
  4. Deployment页签点+,Artifact选你Web项目的war exploded(推荐,支持热部署)
  5. Application context填你希望的访问路径,比如/demo
  6. Server页签里On 'Update' action选Update classes and resources

需要注意的是:IDEA里配置的Tomcat一定要和项目要求的JDK、Tomcat版本匹配,否则启动时会报ClassNotFoundException或者各种版本冲突。另外war exploded比war更适合开发调试,改动代码能自动热更新,不用反复重启。

4.4 部署脚本示例

生产环境手动部署WAR包时,我习惯写一个简单的部署脚本,包含备份和回滚能力:

#!/bin/bash APP_NAME=example APP_WAR=/data/uploads/example.war WEBAPPS=/opt/tomcat/current/webapps BACKUP_DIR=/data/backups/$(date +%Y%m%d%H%M%S) mkdir -p $BACKUP_DIR if [ -f $WEBAPPS/${APP_NAME}.war ]; then cp $WEBAPPS/${APP_NAME}.war $BACKUP_DIR/ fi if [ -d $WEBAPPS/${APP_NAME} ]; then mv $WEBAPPS/${APP_NAME} $BACKUP_DIR/ fi cp $APP_WAR $WEBAPPS/ echo "部署完成,当前版本备份在: $BACKUP_DIR"

这个脚本虽然简略,但“先备份再覆盖”这个思路很重要。出了问题能快速回滚,比临时找旧包去下载快了不止一个数量级。

5. 运维实战:自启动、JVM调优、日志与容器化

5.1 Linux下Tomcat自启动:systemd配置

手动startup.sh启动的服务,服务器一重启就没了。生产环境必须做成systemd服务。

在/etc/systemd/system/tomcat.service写入以下内容:

[Unit] Description=Apache Tomcat Web Server After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=JAVA_HOME=/usr/local/java/jdk1.8.0_421 Environment=CATALINA_HOME=/opt/tomcat/current Environment=CATALINA_BASE=/opt/tomcat/current ExecStart=/opt/tomcat/current/bin/startup.sh ExecStop=/opt/tomcat/current/bin/shutdown.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

然后执行:

systemctl daemon-reload systemctl enable tomcat systemctl start tomcat systemctl status tomcat

提示:Type=forking很重要。Tomcat的startup.sh会fork一个子进程后父进程退出,systemd需要知道怎么判断服务启动是否完成。forking模式会检查PID文件来判断启动状态,Tomcat默认把PID写入catalina.pid?实际上Tomcat并不会自己生成PID文件,需要在catalina.sh里设置,或者用Type = oneshot让ExecStart执行完后服务即处于启动状态。更稳妥的做法是在服务文件里加Environment=CATALINA_PID=/opt/tomcat/current/temp/catalina.pid,这样shutdown.sh / systemctl stop才能正确找到主进程PID,否则停止服务时可能出现“kill不到进程”的现象。

我自己实际测试下来,Type=forking+CATALINA_PID是兼容性最好的组合。如果你发现systemctl stop后8080端口还活着,九成是PID文件没配好。

5.2 JVM参数调优:setenv.sh的正确姿势

Tomcat的JVM参数不建议直接改catalina.sh,因为升级版本时这个文件会被覆盖。推荐做法是在bin/下新建一个setenv.sh,Tomcat启动时会自动加载它。

# 文件: /opt/tomcat/current/bin/setenv.sh CATALINA_OPTS="-server -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xss512k -XX:+UseConcMarkSweepGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heapdump.hprof"

参数怎么定?核心原则是先看物理内存和业务量级,再反推参数。一台4核8G的机器,系统预留2G,Tomcat堆给2G比较合理。metaspace里放的是类元数据,默认无上限,那反而危险——如果不设MaxMetaspaceSize,出现类加载器泄漏时Metaspace会一路涨到挤爆物理内存。Xss512k是每个线程的栈大小,线程数多了对内存节约明显。

GC策略的选择现在普遍推荐用G1(JDK 9+默认),命令是-XX:+UseG1GC,而不是传统的老式CMS(JDK 14已经移除了CMS)。我上面示例写的是CMS,是因为JDK 8习惯沿用,如果你用JDK 11+,直接用G1就好:

CATALINA_OPTS="-server -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xss512k -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/logs/heapdump.hprof"

HeapDumpOnOutOfMemoryError这行强烈建议所有生产环境都加上,否则内存溢出后只有一行报错日志,根本没法分析是哪里泄漏。有了heapdump文件,用MAT打开就能看到对象引用链。

5.3 日志体系与日志分析

Tomcat的日志分布在logs/目录下,但很多人分不清每个文件的作用:

文件内容
catalina.out标准输出和错误输出,最重要的日志主文件,启动异常、System.out.println(不建议生产用)、未捕获异常都会到这里
localhost.logHost应用的日志,应用启动失败时的堆栈会记在这里
localhost_access_log.*.txt访问日志,记录所有HTTP请求,做分析报表、审计的原始数据
manager.log / host-manager.logTomcat管理后台的日志

默认情况下访问日志是关闭的,需要在server.xml的Host节点里打开。访问日志对排查线上问题非常重要——有没有人扫你的接口、某个异常流量从哪来、某个慢请求耗了多少时间,都能从这里看出来。配置如下:

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs" prefix="localhost_access_log" suffix=".txt" pattern="%h %l %u %t &quot;%r&quot; %s %b %D" resolveHosts="false"/>

%D是处理请求耗时(毫秒),排查慢接口非常好用。日志格式中的%D在同级别配置中容易被忽略,强烈建议加上。

日志切割用logrotate,避免catalina.out无限膨胀:

# /etc/logrotate.d/tomcat /opt/tomcat/current/logs/catalina.out { daily rotate 7 copytruncate compress missingok }

copytruncate很关键,它先复制日志再清空原文件,不需要重启Tomcat进程,不影响业务。rotate 7保留一周的日志,够用又不占太多磁盘。

5.4 Docker方式部署Tomcat

容器化场景下,Tomcat部署反而更简单。官方镜像tomcat:9.0-jdk8直接可用:

docker run -d \ --name tomcat-app \ -p 8080:8080 \ -v /data/apps:/usr/local/tomcat/webapps \ -e CATALINA_OPTS="-Xms1g -Xmx1g" \ tomcat:9.0-jdk8

需要说明几个镜像内的路径变化:官方Tomcat镜像是基于Debian/Ubuntu的,Tomcat主目录是/usr/local/tomcat,不是常见的/opt/tomcat。如果需要自定义配置文件,可以把server.xml挂进去:

docker run -d \ --name tomcat-app \ -p 8080:8080 \ -v /data/conf/server.xml:/usr/local/tomcat/conf/server.xml \ -v /data/apps:/usr/local/tomcat/webapps \ tomcat:9.0-jdk8

生产环境用Docker部署时,建议把日志目录也挂到宿主机,否则容器一删日志就没了:

-v /data/logs/tomcat:/usr/local/tomcat/logs

镜像版本注意加tag,不要用tomcat:latest,因为latest会随时间漂移——上周的latest是10.1这周就变成11了,应用可能直接起不来。reproducibility是生产环境第一原则。

6. 常见问题与排查技巧实录

6.1 端口占用:明明能启动,但访问就是不通过

症状:startup.sh提示启动成功,但浏览器访问8080超时或显示拒绝连接。常见原因两条:防火墙拦截和端口被其他进程占用。

排查手段:

# 查端口监听状态 netstat -tlnp | grep 8080 # 或 ss -tlnp | grep 8080 # 找到占用进程后 lsof -i :8080

如果发现8080被别的服务占用,要么改Tomcat的conf/server.xml端口号,要么处理占用进程。还有一种隐蔽情况——Tomcat启了两次,第二次启动报端口占用但记录在catalina.out里,你以为没起来,其实第一个进程还在正常跑。这时候ps -ef | grep java看一下有几个Java进程。

防火墙这边,CentOS上用firewall-cmd --permanent --add-port=8080/tcp && firewall-cmd --reload放通端口,Ubuntu上检查ufw status。互联网云服务器别忘安全组,这是最容易忽略的一环。

6.2 乱码问题:中文变问号,到底哪里起了作用

Tomcat乱码是个经典问题,但乱码的具体场景不同,解法也不同:

现象原因解决方案
catalina.out里的日志中文乱码系统默认字符集不是UTF-8在setenv.sh里加JAVA_OPTS="-Dfile.encoding=UTF-8"
网页显示中文乱码JSP页面编码或响应头编码不对确保<% page pageEncoding="UTF-8" %>,统一用UTF-8
GET请求参数中文乱码连接器URIEncoding未设置server.xml里URIEncoding="UTF-8"
POST请求表单乱码请求体编码不对加字符编码过滤器(Spring的CharacterEncodingFilter或手写Filter)

catalina.out乱码的根源在JVM读取系统默认字符集,Linux下如果locale是POSIX,默认可能是ANSI_X3.4-1968,Java按这个编码输出中文自然变乱码。加-Dfile.encoding=UTF-8是治本。

还有一个容易被忽略的地方:conf/logging.properties里配置的编码。Tomcat 9的logging.properties默认是UTF-8,但如果你在Windows上跳过tar包直接从Windows复制配置,文件本身可能被改成GBK编码,日志就集体乱码。用file命令看一下配置文件的编码,必要时转成UTF-8。

6.3 404错误:“源服务器未能找到目标资源的表示”

开头那句搜索引擎里高频出现的话——源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的——其实就是Tomcat返回的404错误页面。虽然这个描述写得很委婉,但本质是资源不存在。

排查路径按顺序走:

  1. 部署路径是否正确:访问根路径还是/项目名/xxx?webapps/下的文件名决定访问路径。把example.war放进webapps后,Tomcat会自动解压成example/目录,访问入口是http://IP:8080/example/,直接访问http://IP:8080/只会看到Tomcat首页。
  2. 项目的web.xml匹配是否覆盖了你的URL:Servlet的url-pattern如果只写了/api/*,访问/index.html自然404。
  3. Spring MVC的路径映射:前后端分离时,后端Controller有没有写@RequestMapping?路径大小写是否一致?Linux文件系统区分大小写,/Demo和/demo是两个完全不同的路径。

热词里“idea tomcat 描述 源服务器未能找到目标资源的表示”对应的场景——IDEA里启动Tomcat后,浏览器里项目页面显示404,十有八九是IDEA的Application context配错了,看下部署页签里应用的访问根路径和实际请求URL是否匹配。

6.4 内存溢出:OutOfMemoryError怎么定位

生产环境最怕的就是java.lang.OutOfMemoryError。常见形态有两种:

  • Java heap space:堆内存不够,多发于业务高峰期、缓存未清理、循环创建大对象。
  • Metaspace:类元数据占满,多发于频繁热部署、自定义类加载器没有回收。

盯着日志里的关键字还不够,内存分析得抓现场。在setenv.sh里加了-XX:+HeapDumpOnOutOfMemoryError以后,溢出时自动生成hprof文件,用Eclipse MAT或者JProfiler打开,看支配树(Dominator Tree)里最大的对象是什么,就能快速定位到具体的业务代码。

如果不想等溢出,主动用jmap -dump:live,format=b,file=/tmp/img.hprof PID抓现场,配合jstat -gcutil PID 1000看GC曲线,大概率能找到元凶。

6.5 开发环境里一个容易混淆的“GET链接不能用”

热词里有一条“tomcat get链接不能用|”。展开来讲,这往往是符号链接问题——WAR包部署到webapps目录后,Tomcat自动解压的目录可能是软链接指向外部目录,如果配置了Context docBase指向软链接路径,或者直接把外部目录软链接进webapps,Tomcat默认allowSymlinks是false(JDK 8u191后),访问链接就报404或禁止访问。解决方案有两条路:一是把docBase直接指到真实物理路径,不要软链接;二是确实需要软链接时,在conf/context.xml里给Context加allowLinking="true"。不过从安全角度还是建议用物理路径更稳妥。

7. 安全加固的一些实用建议

Tomcat部署上线前,下面的安全项值得过一遍:

  • 修改默认端口:把8080改成不常见端口,虽然防不了真正的扫描器,但能减少大量基础扫描流量。
  • 关闭Manager、Host-Manager管理界面:生产环境用不到就删掉webapps/manager和webapps/host-manager,或者用tomcat-users.xml强制强密码并限制网段访问。
  • 隐藏版本信息:404和500错误页面默认会显示Tomcat版本号,攻击者可以据此查找对应漏洞。在conf/web.xml里加自定义错误页面覆盖掉。
  • 尽量减少使用root运行:本文前面建的tomcat账号就是为了这个目的。

安全是个大话题,但最基本的三四条做扎实,能挡掉大部分低水平扫描,把精力集中在真正的攻防对抗上。

8. 一些想说的扩展方向和心得

Tomcat部署这个主题,表面上看是“装个包跑起来”,但实际延伸到工程层面可以串起一整条链路:版本选型、目录规划、参数调优、日志采集、容器化、与Nginx配合做负载均衡、配合Prometheus + Grafana做监控……任何一个环节都值得单独深入。

最后分享一下我个人的体会:部署这类事情,最重要的不是背命令,而是理解“为什么”。为什么清缓存要删work/目录而不是webapps/?为什么改了server.xml要重启才生效?为什么WAR包解压目录不能随便删?想明白这些底层机制,遇到问题就不会慌,因为你已经有了从原因推出结果的能力。以后我自己接手一台陌生服务器,第一件事永远是看版本、看日志路径、看启动脚本——把“现状是什么”摸清楚,再谈“怎么改”。这也是部署老手和新手之间最本质的区别。希望这篇东西能帮你少走几个弯路,跑出自己的第一个稳定服务。

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

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

立即咨询