news 2026/10/14 17:17:18

Linux下Tomcat安装部署、配置与性能调优实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux下Tomcat安装部署、配置与性能调优实战指南

1. 安装前的准备:版本选型与JDK匹配

先聊个很现实的问题:很多人装Tomcat是直接从网上下一个最新版,解压、启动、完事。短期看没问题,等放到生产环境跑一阵子,各种坑就冒出来了。我见过太多因为版本选型失误导致的线上事故,所以想认真讲一次完整、可靠、能直接照做的安装与配置流程。

这篇指南适合谁?刚接触Linux部署的开发者,需要把存量Java Web项目迁到Linux服务器的运维,以及想把Tomcat纳入体系化管理的团队。内容不追求花哨,每一步背后我都会讲清楚“为什么这么做”,因为只有理解了原理,遇到异常时才不会两眼一抹黑。

1.1 JDK版本与Tomcat版本如何对应

Tomcat本质是一个Servlet容器,它运行在Java虚拟机之上,所以第一步一定是装JDK。很多新手栽跟头就栽在版本匹配上:JDK太新、Tomcat太旧,启动时直接报UnsupportedClassVersionError;反过来JDK太老,Tomcat新版本要求的Java API根本不存在。

我的建议是先把版本对应关系钉死,不要追新。

Tomcat版本Servlet规范最低JDK版本推荐JDK版本备注
Tomcat 10.1.xServlet 6.0JDK 11JDK 17 LTSJakarta EE,包名从javax改为jakarta
Tomcat 9.0.xServlet 4.0JDK 8JDK 8/11存量项目最多,javax包名
Tomcat 8.5.xServlet 3.1JDK 7JDK 8老旧项目常见
Tomcat 7.0.xServlet 3.0JDK 6JDK 7/8仅维护,不推荐新装

这里有个关键点:Tomcat 10开始包名从javax.servlet变成了jakarta.servlet。如果项目里还写着import javax.servlet.http.HttpServlet,直接扔到Tomcat 10上编译期就过不去,更别提运行了。所以升级大版本之前,先确认项目依赖的Servlet API是哪个时代的东西。

JDK本身也有区别。生产环境我一般倾向于用一个稳定的OpenJDK发行版,不要今天换一家明天换一家。用java -version确认安装成功后,务必把JAVA_HOME环境变量写进/etc/profile或用户级配置里,因为Tomcat启动脚本catalina.sh会靠它找Java。

1.2 下载Tomcat与校验安装包完整性

下载Tomcat建议去官网或者可信的镜像站,不建议从第三方博客的网盘链接下包,因为你无法保证包有没有被改动过。拿到压缩包之后,先做两件事:验证SHA512校验和、确认文件大小。

比如官网的下载页面会提供.sha512文件,在Linux下可以这样校验:

# 先下载tomcat压缩包和对应的sha512文件 wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.xx/bin/apache-tomcat-9.0.xx.tar.gz wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.xx/bin/apache-tomcat-9.0.xx.tar.gz.sha512 # 校验(具体命令以实际环境为准) sha512sum apache-tomcat-9.0.xx.tar.gz cat apache-tomcat-9.0.xx.tar.gz.sha512

如果输出和文件内容不一致,那这个包基本可以判定被改过或者下载过程中损坏了,直接重新下载。这个过程看着多余,生产环境里真的值钱,我遇到过镜像同步不完整导致解压后缺文件的怪问题,排查了半小时才发现是包的问题。

另外注意发行版的软件源里通常也有tomcat,但版本一般偏旧,而且OpenJDK联动关系未必是你要的。除非只是本地开发调试,否则我还是推荐官网手动安装,可控性强,升级也方便。

2. 核心安装步骤与目录结构

版本和JDK定下来之后,安装本身反而简单。整个流程大概三步:创建专用用户、解压到指定目录、配置环境变量。

2.1 为什么不用root直接跑Tomcat

这是一个安全习惯问题,也是很多初学者的盲区。Tomcat作为一个对外提供服务的Web容器,如果以root身份运行,一旦应用出现远程代码执行之类的漏洞,攻击者拿到的就是root权限,后果不用多说。所以在Linux上跑任何Web服务,我都建议单独建一个低权限用户。

# 创建tomcat用户,指定家目录,不创建登录shell(提高安全性) sudo useradd -r -s /sbin/nologin -d /opt/tomcat tomcat

这里用了-r创建系统账户,-s /sbin/nologin表示这个用户不能登录终端,只能被服务调用。后面的-d指定家目录为/opt/tomcat,方便统一管理。如果你有特殊的脚本执行需求,可以改用/bin/bash,但从安全角度不建议。

2.2 解压、目录规划与软链接

我习惯把Tomcat解压到一个带版本号的目录里,然后用一个不带版本号的软链接指向它。这样做的好处非常明显:以后升级时只要解压新版本,把配置拷过去后切换软链接,就能快速回滚到旧版本,整个过程近乎原子操作。

# 解压到/opt目录 sudo tar -zxvf apache-tomcat-9.0.xx.tar.gz -C /opt # 创建统一的软链接,后续配置都基于该路径 sudo ln -s /opt/apache-tomcat-9.0.xx /opt/tomcat # 修改目录属主,让tomcat用户能读写 sudo chown -R tomcat:tomcat /opt/apache-tomcat-9.0.xx /opt/tomcat

为什么要改属主?Tomcat运行过程中要往logs里写日志、往temp里写临时文件、往work里写编译后的JSP,如果目录属主是root,tomcat用户就没法正常工作。这里有个细节:如果你把应用部署在webapps里,应用本身也可能需要写文件,那webapps目录也必须保证tomcat用户可写。但反过来,conf目录我建议只读,防止应用运行时修改配置。

2.3 环境变量与Java路径

Tomcat不一定非要配环境变量才能跑,它只要能找到JAVA_HOME就行。最省心的方式是写一个/etc/profile.d/tomcat.sh,让所有终端都自动加载:

sudo tee /etc/profile.d/tomcat.sh > /dev/null <<'EOF' export CATALINA_HOME=/opt/tomcat export PATH=$PATH:$CATALINA_HOME/bin EOF source /etc/profile.d/tomcat.sh

如果系统里安装了多个JDK,我强烈建议在catalina.sh或者setenv.sh里显式指定JAVA_HOME,而不是完全依赖PATH环境变量。因为PATH可能会被修改,或者某个服务启动时环境变了,就导致Tomcat用了错误的JDK。实话说,在setenv.sh里写JAVA_HOME是最稳的做法,后面调优那一节还会详细讲。

2.4 Tomcat目录结构速览

解压之后你会看到一堆目录,它们的职责必须分清,不然排查问题时方向都会跑偏。

目录作用我的使用建议
bin启动、关闭脚本不要改catalina.sh,自定义参数放setenv.sh
conf全部配置文件改动前先备份
libTomcat自身和公共依赖jar除非必要,不要乱加jar
logs运行日志排查问题的第一现场
temp临时文件目录不用管
webappsWeb应用部署目录默认应用建议删除或改名
workJSP编译后的临时目录更新类文件后有时需要清空这里

备注里提到的“默认应用建议删除”,很多人可能不理解。Tomcat自带的ROOT、docs、examples这些应用属于演示性质,对外网暴露会有安全风险,合规检查里经常被揪出来。我装完第一个动作就是把它们清理掉:

sudo rm -rf /opt/tomcat/webapps/docs /opt/tomcat/webapps/examples sudo rm -rf /opt/tomcat/webapps/ROOT

之后再把webapps留空,等着部署真实应用。

3. 启动流程、验证与开机自启

安装做到这里,结构已经完整了,接下来是把服务跑起来,并且让它在重启后自动拉起。Linux下管理服务的方式有很多种,CentOS 6时代用init脚本,CentOS 7之后基本都用systemd。我下面的做法以systemd为主,这是当前主流Linux发行版的默认方案。

3.1 手动启动与冒烟验证

第一次安装完Tomcat,不要急着写systemd配置,先手动起一次确认能不能正常跑通:

sudo -u tomcat /opt/tomcat/bin/startup.sh

注意我这里用sudo -u tomcat,让服务以tomcat身份启动。如果直接用root启动,之后写systemd配置时用户切换可能会有残留文件权限问题。

启动后验证方式有三个层次:

  • 看进程:ps -ef | grep tomcat,确认java进程存在
  • 看端口:ss -tlnp | grep 8080,确认监听端口正常
  • 看日志:tail -f /opt/tomcat/logs/catalina.out,确认没有异常堆栈

访问验证时先别急着用浏览器,先用curl在本地探一下:

curl -I http://127.0.0.1:8080

如果返回200或者相应的HTTP状态码,说明服务已经就绪。如果这一步通了但外部机器访问不了,问题通常出在防火墙或者云安全组,而不是Tomcat本身。

3.2 编写systemd服务单元

手动启动没问题后,就该把Tomcat交给systemd了。在/etc/systemd/system/下新建tomcat.service,内容如下:

[Unit] Description=Apache Tomcat Web Application Container After=network.target [Service] Type=forking User=tomcat Group=tomcat Environment=JAVA_HOME=/usr/lib/jvm/你的JDK路径 Environment=CATALINA_HOME=/opt/tomcat Environment=CATALINA_BASE=/opt/tomcat Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid Environment=CATALINA_OPTS=-Xms512M -Xmx1024M -Djava.security.egd=file:/dev/./urandom ExecStart=/opt/tomcat/bin/startup.sh ExecStop=/opt/tomcat/bin/shutdown.sh ExecReload=/bin/kill -s HUP $MAINPID SuccessExitStatus=143 Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

这段配置里有几个“为什么”值得说细一点:

Type=forking:Tomcat的startup.sh启动后会fork出一个守护进程,然后shell本身退出,所以必须用forking类型,systemd才能正确跟踪主进程。

Environment=JAVA_HOME=/usr/lib/jvm/你的JDK路径:这条就是为了防止系统PATH变化导致Tomcat找不到Java。JDK路径用update-alternatives --list java或readlink -f $(which java)查出来之后填进去。

CATALINA_PID:shutdown.sh需要PID文件才能找到进程。Tomcat默认不会写PID文件,但通过这个环境变量设置后,startup.sh就会自动创建。

ExecStop:用shutdown.sh做优雅停机,它会等待所有线程结束,比直接kill多了个缓冲过程,避免突然杀掉还在处理请求的线程。

SuccessExitStatus=143:Java服务在接收SIGTERM时默认退出码是143,如果不加这一行,systemd会认为服务是异常退出。

写完之后重载配置并启动:

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

enable很重要,它会创建开机启动的软链接,以后机器重启Tomcat会自动拉起。注意如果之前手动用startup.sh启动过同一实例,要先shutdown.sh停掉,否则systemd启动时会因为端口被占而失败,或者出现PID冲突。

3.3 启动失败时先看这里

systemd写完后启动失败,最常见的原因是端口被占用。8080被占用的概率很高,尤其你机器上可能跑了其他Java服务。三步排查:

# 1. 查看端口监听情况 ss -tlnp | grep 8080 # 2. 直接看Tomcat自己的日志 sudo tail -n 100 /opt/tomcat/logs/catalina.out # 3. 看systemd记录的启动状态 sudo journalctl -u tomcat --since "5 minutes ago"

如果在catalina.out里看到Address already in use,那基本就是端口冲突。要么把占用8080的进程关掉,要么修改Tomcat端口。别一上来就怀疑配置写错了,先看日志是最节省时间的路径。

4. 深入拆解server.xml的核心配置

Tomcat的配置中心是conf/server.xml,几乎所有对外行为都在这个文件里定义。很多人的做法是遇到问题直接搜代码改,但我建议先花二十分钟把结构看懂,之后无论调优、加虚拟主机、改HTTPS都能手到擒来。

4.1 先理清server.xml的分层结构

server.xml的核心结构其实是一个树:Server是最顶层,代表整个Tomcat实例;Server里包含多个Service;每个Service包含若干个Connector和一个Engine;Engine里包含多个Host;Host里包含多个Context。有点像套娃,但每层职责非常清晰:

  • Connector:负责接收外部请求,可以理解成入口大门,通常一个HTTP连接器一个AJP连接器
  • Engine:请求处理的引擎,负责把请求交给哪个Host
  • Host:虚拟主机概念,对应一个域名或IP,路径匹配靠它
  • Context:一个独立的Web应用,一个Host下可以挂很多Context

Server.xml里第一个节点往往是<Server port="8005" shutdown="SHUTDOWN">,这个8005端口是管理关闭指令的监听端口,任何人只要能连上并且发送字符串“SHUTDOWN”(默认值),Tomcat就会退出。生产环境如果不做保护,这是个安全隐患,我的做法是把shutdown字符串改成一个随机较强的字符串,或者把port设为-1直接禁用远程关闭功能。

4.2 Connector连接器的关键参数

对外提供HTTP服务的是这行配置:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" minSpareThreads="10" acceptCount="100" URIEncoding="UTF-8" />

选几个容易踩坑的讲。

maxThreads:Tomcat处理请求的最大线程数。线程数不是越大越好,你设成1000,如果每次请求耗时2秒,CPU和内存早就被拖垮了。我一般建议按单请求平均耗时的反比估算,比如目标是并发200,单请求平均100ms,那maxThreads设200-300比较合理。更稳的做法是用压测工具先摸底再调整。

acceptCount:线程池满了之后,操作系统还能接收多少请求排队。设太小,高峰期直接把请求拒了;设太大,用户长时间排队感知更差。一般100左右起步,配合前面的线程数一起调。

connectionTimeout:连接超时时间,单位毫秒。20000就是我们常说的20秒。太短的话,慢客户端下载大文件时容易断开。

URIEncoding:必须设成UTF-8,不然带中文参数的URL会出现乱码。这个坑在老旧系统里太常见了,很多项目最后查出来就是这里没配。

4.3 虚拟主机与部署路径映射

默认配置里只有一个<Host name="localhost">,appBase指向webapps目录。如果一台机器要跑多个域名、多个应用,可以加虚拟主机,也可以直接用Context改变应用访问路径。

用Context把应用映射到根路径是最常用的手法。假设你的war包叫myapp.war,默认访问路径是http://ip:8080/myapp/,如果想让用户直接访问根路径,可以这样定义:

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true"> <Context path="" docBase="/opt/apps/myapp" reloadable="true"/> </Host>

这里docBase指向了webapps之外的目录,好处是应用代码和Tomcat安装目录分离,升级Tomcat时不怕应用被误删。reloadable="true"表示类文件变化后自动重载,开发环境很方便,生产环境我一般设false,因为自动重载会带来不确定的类加载器问题。

4.4 配置文件的备份与排错纪律

改server.xml之前,先存一个带日期的备份:

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

Tomcat对server.xml的格式很敏感,多写一个标签或者写错一个属性,启动时可能直接起不来。修改后一定要执行/opt/tomcat/bin/configtest.sh验证配置,它会告诉你“Syntax error”或者“OK”,等这一关过了再重启。

我在实际项目里还遇到过一个很隐蔽的问题:某个节点属性名拼写错误,Tomcat启动时不报错,但那项配置完全不生效,导致定位问题浪费了很多时间。所以尽量在改动后用curl验证行为是否符合预期,比如改了URIEncoding就测一次带中文参数的实际请求。

5. 应用部署:war包、目录方式与数据源

安装和配置都做完之后,真正的日常工作就是部署应用了。这一步上手简单,但有很多细节会影响线上稳定性和排障速度。

5.1 war包部署与热部署机制

Tomcat最简单粗暴的部署方式就是把war包放进webapps目录,如果autoDeploy="true"(默认开启),Tomcat会定期扫描目录,发现新的war包自动解压并启动。unpackWARs="true"则允许把war解压成目录。

这种方式的坑在于:war包是有状态的,更新的时候你应该先停掉应用,替换war包,再启动。有一些团队图省事直接覆盖war包,Tomcat检测到变化会尝试重新部署,但正在运行的类很容易出现新旧混用、一部分请求走到新逻辑一部分走到旧逻辑,线上偶发诡异Bug。我踩过这个坑之后形成的纪律是:生产环境更新war包必须走“停-替-启”三步。

另外注意,删除war包并不等于删除已解压的目录。手动清理时需要把webapps下的同名目录一并删掉,否则Tomcat重新扫描时会从目录启动,让你误以为“war删了但服务还在”。这个现象太常见了,困惑过很多人。

5.2 用Context指向外部目录部署

前面在server.xml里提到过Context的用法,实际运维中我更倾向这种外部目录部署方式。具体步骤是把发布包放到一个独立的发布目录,比如/data/apps/myapp/,然后在server.xml或单独的Context配置里指向它:

<Context path="/myapp" docBase="/data/apps/myapp" reloadable="false" />

这样做的好处有三个:

  • 应用、Tomcat、日志目录彻底分离,Tomcat升级或回滚不会动到应用
  • 发布新版本时可以先备份旧目录再替换新目录,如果启动失败,改一下Context指向就能秒级回滚
  • webapps目录不会因为大量应用堆积而失控,目录扫描效率也更高

回滚的具体做法是保留/data/apps/myapp_bak,新版本放到/data/apps/myapp_20250401,然后改Context的docBase指向新目录,重启。一旦发现异常,改回myapp_bak即可。这种思路很朴素,但比依赖war包热部署要稳得多。

5.3 配置数据源而不是在应用里管理连接池

Java Web应用连接数据库时,很多人习惯在应用里配置连接池,但Tomcat自身就提供了JNDI数据源能力,集中管理连接池更利于运维和调优。在conf/context.xml的Context标签里加入以下内容:

<Resource name="jdbc/MyDB" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/yourdb?useSSL=false&characterEncoding=utf8" username="youruser" password="yourpassword" maxTotal="50" maxIdle="10" maxWaitMillis="10000" />

同时需要把数据库驱动jar(比如MySQL Connector/J)放到lib目录下,因为JNDI数据源是在Tomcat容器层建立的,应用自身classpath里没有这个驱动是没法用的。

应用侧通过JNDI查询获取数据源:

Context initCtx = new InitialContext(); DataSource ds = (DataSource) initCtx.lookup("java:comp/env/jdbc/MyDB"); Connection conn = ds.getConnection();

配置数据源时的常见误区是把连接池参数设得非常大,以为越大吞吐越高。实际上连接数超过数据库负载能力后,瓶颈会变成数据库侧的CPU和锁竞争。我一般在maxTotal=50起步,压测后再根据响应时间和数据库负载逐步调整。

6. JVM参数调优与基础性能优化

装好了、应用部署上去了,如果响应慢、经常卡顿,很多问题都出在JVM参数不合理上。Tomcat本身不调JVM也能跑,但要扛住线上流量,这几项必须做。

6.1 setenv.sh的用法与常见设置

Tomcat的catalina.sh是核心启动脚本,但官方明确建议不要直接改它,因为升级Tomcat时脚本会被整体替换。自定义环境变量应该放在bin/setenv.sh,Tomcat启动时如果发现这个文件存在,会自动加载。

sudo tee /opt/tomcat/bin/setenv.sh > /dev/null <<'EOF' export JAVA_HOME=/usr/lib/jvm/你的JDK路径 export CATALINA_HOME=/opt/tomcat export CATALINA_BASE=/opt/tomcat export CATALINA_OPTS="-Xms512M -Xmx1024M -XX:MetaspaceSize=128M -XX:MaxMetaspaceSize=256M" export JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djava.security.egd=file:/dev/./urandom" EOF sudo chown tomcat:tomcat /opt/tomcat/bin/setenv.sh sudo chmod +x /opt/tomcat/bin/setenv.sh

为什么要拆成CATALINA_OPTS和JAVA_OPTS?因为JAVA_OPTS里的参数会被所有Java命令继承,包括shutdown.sh;而CATALINA_OPTS只用于启动Tomcat主进程。比如-Xms这样的堆内存参数放到CATALINA_OPTS里更合理,因为关闭进程时用不着一大堆堆内存参数。

6.2 如何估算初始堆与最大堆

老生常谈的-Xms和-Xmx,经验法则是最小堆和最大堆最好相等,避免运行期频繁扩容和收缩带来的停顿。但这不代表你可以无脑设大,因为堆内存是物理内存的一部分,你给Tomcat 4G,其他服务可能就饿死了。

一个粗估公式是:堆内存 ≈ 对象存活量 × 3到4倍余量。如果不知道对象存活量,就先按物理内存的50%以内来设,留一半给操作系统文件缓存和其他进程。之后用jstat -gc <pid>观察Old区的增长趋势,如果老年代持续高位且频繁Full GC,再决定加内存还是优化代码。

Metaspace也是容易被忽略的点。很多Java 8之前的系统还在用PermSize,但JDK 8之后元空间默认只受物理内存上限约束,不设MaxMetaspaceSize的话,不断加载类的场景(比如热部署、动态生成代理)可能把内存吃透。设一个上限,至少能让进程在出错时表现更可控。

6.3 连接器参数与操作系统的配合

前面server.xml里的maxThreads是一层,操作系统层面也要配合。比如文件描述符限制,如果Tomcat线程数上来、连接数够大,但ulimit -n停留在1024,高并发时就会报Too many open files。

查看并调高限制:

ulimit -n

临时调高可以用ulimit -n 65535,持久化调整要改/etc/security/limits.conf或者systemd服务里的LimitNOFILE。既然讲到了systemd配置,直接在tomcat.service里加一行最省事:

LimitNOFILE=65535

调优不是单点操作,我见过有人只调线程数,没有调文件描述符;也有人只加内存,但GC日志一开发现停顿全在转储阶段。真正有效的做法是调整完一项之后,用压测工具(比如wrk或Apache Bench)观察响应时间的P99和错误率,再决定下一个参数动哪里。

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

最后用我实际遇到过的几个高频问题来收尾,这些问题不是一个团队遇到过,是几乎每个跑Tomcat的人都会碰到。

7.1 服务启动了但页面404

启动日志没有异常,ps能看到Java进程,8080端口也监听着,但无论访问根路径还是应用路径都是404。这种问题大概率是应用没挂载成功。先去webapps目录看有没有解压出的应用目录,如果没有,确认war包是否完整;如果有但页面还是404,重点排查Context路径是不是配错了。

有一次部署某应用时,我在server.xml里配了<Context path="/app" docBase="/data/apps/myapp" />,访问http://ip:8080/app/却一直404,翻日志发现应用的web.xml里配置了display-name和Servlet映射,但Servlet路径和Context的path重复叠加了一层。这种问题急不来,一条链路一条链路地拆。排查顺序建议:先curl根路径确认Tomcat本身OK,再curl应用路径确认路由正确,最后看应用自己的日志。

7.2 端口被占用与AJP连接器问题

默认配置里Tomcat开了两个连接器:一个HTTP/1.1连接器在8080,一个AJP连接器在8009。有些项目用不到AJP协议(尤其是Nginx反向代理时基本都是走HTTP),但8009端口仍然会监听出去,这也是不少人排查端口占用时的干扰项。

处理方式有两种。第一,如果确认不需要AJP,直接注释掉AJP Connector,缩小暴露面;第二,如果必须用,改掉默认端口避免冲突。我见过某个安全扫描报告专门盯AJP的漏洞,跟业务方确认后直接禁用了AJP,线上毫无影响。

查找端口占用的命令推荐两个:

# 精确查看8080端口被谁占用 lsof -i :8080 # 或者用ss命令,输出更可读 ss -tlnp | grep 8080

看到PID之后用ps -p <pid> -f确认是不是自己的Java进程,防止误杀。

7.3 内存溢出与GC日志分析

系统跑一段时间后开始卡顿,最后日志里出现java.lang.OutOfMemoryError: Java heap space或GC overhead limit exceeded,这是经典的内存问题。

先别急着加内存,打开GC日志看模式。临时启用GC日志可以这样做:

sudo -u tomcat tail -f /opt/tomcat/logs/gc.log # 如果没开,可以修改setenv.sh后重启 export CATALINA_OPTS="$CATALINA_OPTS -Xloggc:/opt/tomcat/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps"

GC日志里如果看到Full GC非常频繁且每次耗时都在几秒级别,说明堆内存已经接近饱和。此时一味调大-Xmx只是把问题往后推,更值得做的是用jmap -histo <pid>看对象分布,找到积压的大对象。有一次我排查一个内存泄漏,观察了很久才定位到是某个静态Map不断往里塞数据,最后清理时只用了不到十行代码。

7.4 启动慢与随机数阻塞问题

有些应用启动时要消耗几十秒甚至几分钟,一种常见原因是JVM在等待熵池,也就是SecureRandom阻塞。最直观的表现是日志卡在某个位置长时间不动,然后突然又继续输出。

规避办法有二选一:一是在Catalina启动参数里加-Djava.security.egd=file:/dev/./urandom,前面systemd配置里已经写过了;二是确保系统安装了haveged熵服务。第二类方案适合对随机数质量要求高的场景,第一类方案在绝大多数Web应用场景中够用,也是我的第一选择。这个问题在虚拟机、云主机上尤其容易出现,物理机上没那么明显。

7.5 应用日志中文乱码

Tomcat层面设置了URIEncoding="UTF-8",但控制台和应用日志仍然乱码,多半是文件编码和Tomcat自身编码对不上。确认三个地方:操作系统的locale是否为UTF-8,JAVA_OPTS里是否加了-Dfile.encoding=UTF-8,应用日志框架的输出编码是否设为UTF-8。三个地方都确认后,乱码问题基本能消失。

8. 一些属于自己的运维习惯总结

这些年在Linux上部署Tomcat,踩过的坑不少,也慢慢沉淀下来一套自己的“规范动作”。首要是把版本固定下来,JDK、Tomcat的版本都要有记录,不要今天用8明天升到11,环境漂移造成的隐性问题有时候比代码Bug更难排查。版本记录不只是记个数字,还要把关键变更原因写清楚,比如“为什么升到Tomcat 9”比“升到9”更有用。

第二个习惯是配置全部纳入版本管理。server.xml、setenv.sh这些文件全部放到配置管理仓库里,一旦哪台机器被改乱了,diff一下立刻知道问题在哪。没有这层管理,线上环境很容易变成只有你自己认识的“黑箱”。

第三个习惯是回滚预案比升级预案更重要。每次做版本升级前,我会准备好一条明确的回滚路径:旧安装包保留、配置备份、软链接切换顺序都写成文档。真出事的时候照着执行,就不会手忙脚乱。

最后分享一个小技巧:Tomcat跑稳定之后,把mtime、权限、目录结构这些静态信息记录成一份基线文件,下次做系统体检时拿来对比,文件多了少了、权限被改了都能第一时间发现。这套思路不只是给Tomcat用,任何长期运行的中间件都适用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 17:15:30

RK3288W UBoot阶段双GPIO上拉配置指南

最近调试一块RK3288W的方案板&#xff0c;遇到一个很典型的状况&#xff1a;代码编译通过、UBoot也能正常跑起来&#xff0c;但用万用表量两个需要内部上拉的GPIO引脚&#xff0c;电压始终是0V。查来查去&#xff0c;问题出在上拉寄存器根本没配对。RK3288W这颗SoC上跑Android …

作者头像 李华
网站建设 2026/10/14 17:15:30

Vue.js入门:从响应式原理到工程化项目实战

1. 为什么我建议把Vue.js作为前端入门的第一框架去年有位刚转行的朋友问我&#xff0c;零基础学前端&#xff0c;第一套框架到底选什么。我当时的回答很简单&#xff1a;如果你没有特殊偏好&#xff0c;就先学Vue.js。这不是因为我只用Vue&#xff0c;而是因为我在带过几轮新人…

作者头像 李华
网站建设 2026/10/14 17:14:24

死锁排查与预防实战:从线程快照到锁顺序优化

前言死锁这玩意儿&#xff0c;像程序世界里的灵魂绑架。线程们互相攥着对方想要的锁&#xff0c;谁也不撒手&#xff0c;然后一起卡死在那里&#xff0c;既不报错也不退出&#xff0c;CPU 占用看着还挺正常甚至接近 100%&#xff0c;可业务就是一动不动。我见过不少项目&#x…

作者头像 李华
网站建设 2026/10/14 17:14:01

GitHub日榜趋势监测系统:从原始数据到技术信号提取

1. 这不是“榜单截图”&#xff0c;而是一套可复用的 GitHub 日榜趋势观测系统你有没有试过打开 GitHub Trending 页面&#xff0c;刷出一堆陌生项目&#xff0c;点开 README 又看不懂技术栈&#xff1f;或者想跟踪某个新兴框架的爆发节奏&#xff0c;却只能靠手动刷新、截图、…

作者头像 李华