本地跑得好好的,一部署到服务器就各种问题:端口不通、进程没了、日志找不到、数据库连不上。这套“本地能跑”和“服务器能跑”之间的差距,几乎每一个做过Java后端的人都在某个深夜体会过。今天这篇就用一台阿里云CentOS 7服务器把Spring Boot项目从零部署到能稳定运行,覆盖环境安装、项目打包、进程守护、上线排查这几个环节,全程记录下来,适合第一次接触服务器部署的开发者,也适合想把自己项目从IDE里挪到线上跑起来的同学。
我不能保证看完这篇你的服务器就永远不会出问题,但至少能让你少走我踩过的那些弯。整个过程我会尽量说清楚每个命令为什么这么写,以及哪个环节最容易被忽略。
1. 服务器选型与首次登录:别在第一步就埋下隐患
1.1 实例规格和系统版本的选择逻辑
部署Spring Boot项目,服务器配置不是越高越好,也不是越低越省钱,关键是匹配你的项目规模和预期流量。
先说我自己的经验:如果你只是个人项目、演示Demo、或者一个小型内部系统,2核4G的实例足够用了。Spring Boot默认内嵌Tomcat,JVM堆内存给个512MB到1GB,加上系统本身的开销,4G内存能比较从容地跑起来。2核2G也能跑,但碰到并发稍微上来一点,GC频繁、CPU飙高是常态,排查起来很费劲。
如果是生产环境且有真实的业务流量,建议至少4核8G起步。理由很简单:JVM不是让你把内存全部塞满的,留出给操作系统Page Cache、数据库连接、日志缓冲这些空间,才能避免OOM时候的连锁反应。
系统镜像选择CentOS 7.9,注意几点:一是选CentOS 7系的最新小版本,因为旧版本可能存在已知的内核或安全补丁缺失;二是不要选带“GUI”的版本,纯命令行最小化安装就行,图形界面在服务器上完全是浪费资源。云厂商一般会提供“系统镜像”和“自定义镜像”,第一次部署选系统镜像里的CentOS 7.9即可。
1.2 安全组规则到底该怎么配
这一步很多人会忽略,但90%的“连不上服务器”问题都出在安全组上。
安全组是云厂商在虚拟机外层的防火墙,和服务器内部的firewalld是两层概念,两个地方都要放行,只放行一个都没用。
我建议的安全组配置如下:
| 端口 | 协议 | 授权对象 | 用途 |
|---|---|---|---|
| 22 | TCP | 你自己的IP(别用0.0.0.0/0) | SSH登录 |
| 80 | TCP | 0.0.0.0/0 | HTTP访问(如果有Nginx或直接暴露) |
| 443 | TCP | 0.0.0.0/0 | HTTPS访问 |
| 8080 | TCP | 0.0.0.0/0 | Spring Boot默认端口(看情况开) |
| 3306 | TCP | 仅限自己的IP(或干脆不开) | MySQL远程连接(强烈建议不对外开放) |
关于3306端口多说一句:MySQL端口暴露到公网就是一场灾难。线上被勒索、被删库、被植入挖矿脚本的案例我见过不止一次。最稳妥的做法是永远不给3306开公网安全组规则,需要管理数据库的时候就通过SSH隧道连,或者直接用服务器命令行工具操作。
1.3 SSH连接与密钥登录的细节
服务器创建完成后,第一件事就是登录上去。密码登录虽然简单,但不建议长时间用,一个是每次输入麻烦,二是有被暴力破解的风险。
我习惯的流程是:先用密码登录,然后立刻配置SSH密钥:
# 在本地机器上执行,生成密钥对 ssh-keygen -t rsa -b 2048 -C "your-email@example.com"生成后把公钥传到服务器上:
ssh-copy-id root@你的服务器IP然后在服务器上修改SSH配置:
vim /etc/ssh/sshd_config找到这几项并修改:
PermitRootLogin prohibit-password PasswordAuthentication no PubkeyAuthentication yes修改完重启SSH服务:
systemctl restart sshd注意:改配置之前先测试一下密钥登录是否已经生效。如果你把密码登录关了密钥又连不上,只能通过云厂商的控制台VNC去救,非常麻烦。改配置后不要立刻断开当前的SSH连接,另开一个窗口测试确认能登录再断开。
首次连接还有一个细节:如果遇到连接很慢、输入密码后卡住的情况,大概率是服务器侧的DNS反向解析超时。修改/etc/ssh/sshd_config里的UseDNS no可以解决。
2. Java运行环境部署:JDK安装不能只看“能跑”
2.1 手动安装还是包管理器安装
Spring Boot 2.x 需要JDK 8或更高版本,Spring Boot 3.x 则要求JDK 17。CentOS 7自带的yum源里默认的Java版本是1.8,足够覆盖大多数存量项目。
安装方式有两种:yum直接装,或者手动下载JDK压缩包解压。
我推荐使用yum安装openjdk-devel版本。原因有三点:
- 自动配置环境变量和系统路径,省心
- 安全补丁更新可以通过
yum update统一管理 - 不存在Oracle JDK的授权协议问题,商业使用更省心
对于绝大多数Spring Boot项目,OpenJDK和Oracle JDK在运行性能上几乎没有区别,不要被“一定要用Oracle JDK”的旧观念误导。
2.2 JDK版本选择与配置环境变量
直接安装JDK 8:
yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel-devel这个包非常关键,它包含了javac编译器和完整的JRE运行环境。如果只装不带-devel的基础包,很多工具链会发现找不到javac和JAVA_HOME。
安装完成后验证:
java -version javac -version接下来手动配置JAVA_HOME环境变量。虽然yum安装已经让java命令可用,但很多应用(包括一些部署脚本、Maven、Jenkins)需要读取JAVA_HOME这个变量才能正常工作。
vim /etc/profile在文件末尾追加:
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk export PATH=$PATH:$JAVA_HOME/bin export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar这里要注意:不同的yum安装版本路径可能略有不同,建议先执行readlink -f $(which java)确认真实路径,再写进JAVA_HOME。
配置好后:
source /etc/profile echo $JAVA_HOME2.3 验证安装完成的标准
一个常见的坑:java -version能输出版本号,不代表环境就完全OK了。
我习惯用source /etc/profile之后在同一会话内执行以下验证:
which java which javac echo $JAVA_HOME三个命令都有输出,且JAVA_HOME指向正确的路径,才算真正装好。否则后面启动Spring Boot时用systemd写服务文件,ExecStart里指定了/usr/bin/java这种绝对路径还好,一旦用了$JAVA_HOME相关的变量或脚本,就会因为环境变量没生效而找不到JVM。
另外,安装完系统后第一件事就该是yum update -y。把内核、glibc、openssl这些基础组件更新到最新版本,既是安全需要,也避免后面装MySQL、Redis时因为依赖库版本太低而报错。更新完重启一次服务器,再继续下面的步骤。
3. 数据库与缓存环境部署:项目跑起来之前的“后勤保障”
3.1 MySQL手动安装与初始化
Spring Boot项目基本离不开数据库。CentOS 7默认源里不带MySQL,需要手动配置仓库。
先检查系统里是否有自带的MariaDB,如果有则卸载,避免端口冲突:
rpm -qa | grep mariadb yum remove -y mariadb*然后下载MySQL官方仓库包(如果访问官方源慢,可以用镜像源或云厂商的公共软件源替代):
wget https://repo.mysql.com/mysql57-community-release-el7-11.noarch.rpm rpm -ivh mysql57-community-release-el7-11.noarch.rpm yum install -y mysql-community-server安装完成后启动并设置为开机自启:
systemctl start mysqld systemctl enable mysqldMySQL 5.7及以上版本首次启动时会给root生成了一个临时密码,记录在日志文件里:
grep 'temporary password' /var/log/mysqld.log用这个临时密码登录:
mysql -u root -p然后执行安全初始化。这里一并说明密码策略的问题:MySQL默认安装后validate_password插件启用,要求密码至少8位且包含大小写字母、数字和特殊字符。如果你只是在测试环境,且不想被这个规则折磨,可以先设置一个符合要求的密码,登录后用以下命令调整:
SET GLOBAL validate_password_policy = LOW; SET GLOBAL validate_password_length = 6;在初始化设置密码时,我的建议是测试环境可以放宽,生产环境密码一定要复杂,并且定期更换。宁可多花5分钟设置一个强密码,也别把数据库裸奔在公网上。
3.2 数据库创建与授权的最佳实践
应用需要自己的数据库和账号,不建议直接用root连接业务。创建方式如下:
CREATE DATABASE yourdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; CREATE USER 'youruser'@'localhost' IDENTIFIED BY 'YourPassword123!'; GRANT ALL PRIVILEGES ON yourdb.* TO 'youruser'@'localhost'; FLUSH PRIVILEGES;这里关键点有两个:
第一,字符集一定要指定utf8mb4。项目里如果要存表情符号或生僻字,utf8字符集会报错或者乱码。这一点在创建库的时候就要固定下来,后期再改字符集非常痛苦。
第二,授权范围默认只给localhost。Spring Boot应用和MySQL在同一台服务器上时,连接串就写127.0.0.1,不需要开远程。如果坚持要远程连接(我不推荐),再把localhost改成%,并在安全组严格限制来源IP。
3.3 Redis安装的同时把配置坑填了
Redis在Spring Boot项目里通常承担缓存、Session共享、消息队列这几类角色。CentOS 7可以通过EPEL源直接安装:
yum install -y epel-release yum install -y redis安装后修改/etc/redis.conf:
bind 127.0.0.1 protected-mode yes启动并验证:
systemctl start redis systemctl enable redis redis-cli pingRedis最大的安全风险就是暴露在公网且没有密码。如果应用和Redis在同一台机器上,bind 127.0.0.1就足够了,根本不需要设置密码。如果必须远程访问,至少要做两件事:改默认端口、设置强密码。
另外一个容易被忽视的点:Redis的maxmemory不配置的话,默认64位系统不限制内存使用。一旦缓存数据量暴涨,Redis会把服务器内存吃满,触发OOM Killer把进程杀掉。在redis.conf里按服务器实际内存设置上限:
maxmemory 1gb maxmemory-policy allkeys-lru这个配置建议在一开始就设置好,别等出问题了再补救。
4. 项目打包上传:把一个Spring Boot应用变成可运行文件
4.1 Maven打包与跳过测试的时机
本地项目打包用的是Maven,服务器上不需要装Maven。本地在项目根目录执行:
mvn clean package -DskipTests这里-DskipTests的意思是跳过测试用例,只编译。如果你没有写单元测试,或者测试用例在打包环境中因为缺依赖而跑不起来,用这个参数能节省好几轮构建时间。
注意一个细节:只跳过测试执行和完全跳过测试编译是两回事。-Dmaven.test.skip=true才是连测试代码都不编译,速度更快。但打包前建议至少在本地跑一遍完整测试,确保代码没有明显问题再部署,所以在服务器上追求速度用skipTests即可,在本地CI里还是老老实实跑测试。
打包完成后,在target目录下可以看到两个文件:
your-project-0.0.1-SNAPSHOT.jar(可执行jar包)your-project-0.0.1-SNAPSHOT.jar.original(原始jar,不可直接执行)
Spring Boot的spring-boot-maven-plugin会生成一个包含内嵌Tomcat的可执行jar包。如果你看到.jar.original文件,说明插件配置正常,我们部署用的是不带original后缀的那个。
4.2 上传工具选择与断点续传
上传jar包到服务器有几种方式,各有适用场景:
| 方式 | 命令/工具 | 适用场景 |
|---|---|---|
scp | scp xxx.jar root@IP:/opt/app/ | 最简单,Linux/Mac原生支持 |
rsync | rsync -avP xxx.jar root@IP:/opt/app/ | 支持断点续传,大文件更稳 |
rz/sz | 登录后执行rz选择本地文件 | Windows下比较方便,但大文件容易中断 |
| 宝塔面板 | 文件管理器直接上传 | 有图形界面时比较友好 |
我个人的习惯是rsync,特别是网络不稳定或者文件较大时,rsync支持断点续传和增量传输,失败重来也不会太心疼。小文件用scp就够了。
上传目录建议统一放在/opt/app下。不要把jar包放在/root或者/home下,这是很多人的习惯性操作,但一旦目录权限或备份策略有变,容易出意外。
在服务器上创建目录:
mkdir -p /opt/app4.3 配置文件的环境差异化处理
Spring Boot项目里,application.yml放公共配置,application-dev.yml放本地开发配置,application-prod.yml放生产配置,这是最常规的分环境管理方式。
部署时启动命令里指定profile:
java -jar /opt/app/your-project.jar --spring.profiles.active=prod关键点在于,生产环境的数据库密码、Redis密码这些敏感信息不应该写在application-prod.yml里提交到代码仓库。我见过太多因为代码仓库泄露导致数据库被入侵的案例了。
比较稳妥的做法是:生产环境的敏感配置用环境变量覆盖,或者在服务器上单独放一个配置文件,用--spring.config.additional-location=/opt/app/config/指定外部配置目录。这样代码仓库里的配置只有骨架,真正的密码只存在于服务器上。
数据库连接串的写法也要注意:如果是同机部署,jdbc:mysql://127.0.0.1:3306/yourdb比jdbc:mysql://localhost:3306/yourdb更快,因为localhost在某些系统上会走socket而非TCP,而JDBC驱动对socket的支持并不总是那么顺畅。
5. 进程守护与开机自启:让Java进程“死不了”
5.1 nohup的局限与场景
很多教程教的是一行命令启动:
nohup java -jar your-project.jar > app.log 2>&1 &这行命令确实能启动项目,而且关掉SSH窗口进程不会退出。但nohup有两个硬伤:
- 进程意外挂掉后不会自动重启。Java进程被OOM Killer干掉、代码里
System.exit()被触发、服务器重启等等,都需要你手动登录再启动一次。 - 进程管理能力为零。没有start/stop/restart的概念,想停止服务只能
kill,想重启只能先kill再nohup,完全没有状态管理。
对于临时调试或者一次性跑的demo,nohup可以接受。但作为一个长期运行的服务,强烈建议一开始就使用systemd。
5.2 systemd服务文件写法详解
在/etc/systemd/system/下创建服务文件,这是CentOS 7管理后台服务最标准的方式:
vim /etc/systemd/system/your-project.service内容如下:
[Unit] Description=Spring Boot Application After=network.target [Service] User=root WorkingDirectory=/opt/app ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/app/your-project.jar --spring.profiles.active=prod Restart=always RestartSec=5 SuccessExitStatus=143 [Install] WantedBy=multi-user.target逐个参数说明:
After=network.target:确保网络服务先就绪再启动应用,否则启动时网络还没准备好,应用里如果初始化了HTTP客户端或者注册到注册中心,可能报错。User=root:指定运行用户。生产环境建议单独创建一个应用用户(比如appuser),只给应用目录的读写权限,避免应用被攻破后直接拿到root权限。WorkingDirectory:工作目录。Spring Boot生成日志、临时文件时的相对路径,都以此为基准。如果不设置,默认是/,日志很可能写得到处都是,排查时很难找。Restart=always:进程退出后无论什么原因都自动重启。配合RestartSec=5,避免JVM刚启动就被反复拉起导致CPU飙升。SuccessExitStatus=143:这个很重要。systemd正常停止Java进程时发送的是SIGTERM,JVM默认退出码是143。如果不在这个参数里标记143为正常退出码,每次停止服务后,systemd会认为进程是异常结束的,从而触发Restart=always,导致你明明执行了systemctl stop,几秒后服务又自己起来了。这个坑特别隐蔽,不写清楚很容易被整到怀疑人生。
启用并启动服务:
systemctl daemon-reload systemctl enable your-project.service systemctl start your-project.serviceenable是把服务设置成开机自启,start是立即启动。用systemctl status your-project.service查看运行状态,用journalctl -u your-project.service -f实时查看日志。
5.3 常用运维命令速查
部署完成后,下面这些命令会是你最常用的:
# 查看服务状态 systemctl status your-project.service # 查看日志(实时跟踪) journalctl -u your-project.service -f # 查看日志(最近100行) journalctl -u your-project.service -n 100 # 重启服务 systemctl restart your-project.service # 停止服务 systemctl stop your-project.service另外建议把服务配置文件备份一份。我的习惯是/opt/app/deploy/目录下留一份.service文件副本,并写上简要说明,这样即使系统重装了也能快速恢复部署。
6. 上线后必须处理的四类问题:端口、日志、内存、文件权限
6.1 端口占用与防火墙的排查链路
服务启动后访问不通,这是一个非常常见的现象。排查链路应该是:
第一步,确认进程是否真的在监听端口:
ss -lntp | grep 8080如果没有输出,说明进程没有监听这个端口,回到日志排查启动是否成功。
第二步,确认防火墙状态:
systemctl status firewalldCentOS 7的firewalld默认是开启的,如果它拦截了8080端口,即使云安全组放行了,外面也访问不到。放行端口:
firewall-cmd --zone=public --add-port=8080/tcp --permanent firewall-cmd --reload第三步,确认云安全组规则是否放行。安全组、服务器防火墙、进程监听三个环节,任何一个断了都连不通。
我遇到过一个很有意思的案例:某开发者的服务启动成功、ss -lntp也能看到8080监听、firewalld也放行了,但从公网就是访问不了。最后发现是服务器有多个网卡,服务监听在了内网网卡的IP上,而不是0.0.0.0。Spring Boot默认server.address是0.0.0.0,理论上不会出这个问题,但如果你在配置里显式指定了server.address,就要注意是不是写错了IP。
6.2 没有日志的排查就是盲人摸象
日志是排查问题的第一依据,但前提是日志有配置、有轮转、有保存策略。
Spring Boot用logback记录日志,默认情况下控制台输出。如果用systemd管理进程,journalctl -u能直接看到标准输出,这很方便,但累积多了会占用/var/log/journal的磁盘空间。
生产环境建议在application-prod.yml里配置日志文件:
logging: file: name: /opt/app/logs/app.log level: com.example: INFO同时配置logback的轮转策略,避免单个日志文件无限增长。推荐使用logback-spring.xml覆盖默认配置,设置MaxHistory、totalSizeCap这些参数,按天滚动保留7天,单文件大小限制在100MB。
再看磁盘空间:
df -h日志写满磁盘导致应用崩溃,这个问题太常见了。一定要做日志轮转,或者至少定期清理。
6.3 内存溢出与JVM参数调优
Spring Boot应用的OOM,表现通常是进程突然消失、dmesg里有Out of memory: Kill process,或者日志里有java.lang.OutOfMemoryError。
JVM参数里有两个关键配置:
-Xms256m -Xmx512m-Xms是初始堆大小,-Xmx是最大堆大小。建议两个值相同,避免运行时堆扩容带来的性能损耗。具体大小要根据服务器的总内存和业务量来定,一般建议最多给服务器内存的50%-60%,剩下给操作系统和外部程序用。
排查OOM时,给JVM加两个参数,让它在OOM时自动导出堆转储文件:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/app/logs/heapdump.hprof这样OOM发生时会有现场证据,用jhat或者jvisualvm分析hprof文件就能看到是哪个对象占用最多内存。
另外,Java进程被系统杀掉不一定都是OOM,也可能是系统内存整体不足触发了OOM Killer。排查时先看free -h,再看dmesg,两个方向一起确认。
6.4 文件上传权限问题
Spring Boot应用如果包含文件上传功能,最常见的坑就是“上传成功,但文件没有落盘”或者“前端访问不到上传的文件”。
这类问题的排查核心是目录权限:
# 确认上传目录的属主和权限 ls -ld /opt/app/upload chown -R root:root /opt/app/upload chmod -R 755 /opt/app/upload如果你把应用运行用户改成了非root账号,上传目录必须让该用户可写。这看起来很简单,但在systemd服务里运行时,进程的工作环境和手动SSH登录时完全不同,环境变量、用户权限、工作目录都有差异。
我建议在服务文件里显式指定User,同时确保所有应用需要的目录权限都匹配这个用户。部署完服务后,从页面上传一次文件测试,确认无误再收工。千万别想当然觉得“我在服务器上手动执行时没问题,服务跑起来也不会有问题”。
7. 从“能跑”到“跑得稳”:Nginx反向代理、HTTPS与备份
7.1 Nginx反向代理的必要性
Spring Boot默认监听8080端口,用户直接访问http://IP:8080也可以,但这样做有几点不便:端口号暴露很丑、8080被大量扫描工具盯上、无法做多应用共存的80端口分流。
用Nginx做反向代理,对外统一暴露80端口,内部根据路径或域名转发到不同端口,是最常见的部署形态。
安装Nginx:
yum install -y nginx新建站点配置:
vim /etc/nginx/conf.d/your-project.confserver { listen 80; server_name your-domain.com; location / { 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; proxy_set_header X-Forwarded-Proto $scheme; } client_max_body_size 50m; }client_max_body_size这行很重要。如果项目有文件上传功能,默认Nginx只允许1MB大小的请求体,不调大这个值,大文件上传直接413。
保存后校验配置并重启:
nginx -t systemctl restart nginx7.2 HTTPS配置的一次到位
部署完HTTP能访问后,强烈建议直接上HTTPS。现在浏览器对HTTP的标记越来越严格,加上项目如果涉及登录、支付等敏感操作,HTTPS是必须的。
证书申请有两条路:云厂商的免费证书(通常是一年期)或者手动签发Let's Encrypt证书。无论用哪种,拿到证书文件后配置到Nginx里:
server { listen 443 ssl; server_name your-domain.com; ssl_certificate /etc/nginx/ssl/your-domain.pem; ssl_certificate_key /etc/nginx/ssl/your-domain.key; location / { proxy_pass http://127.0.0.1:8080; # 同上... } }配置完重启Nginx,检查443端口是否监听:
systemctl restart nginx ss -lntp | grep 443HTTPS配置里最容易出的问题是证书路径错误或证书文件拼接错误,多数浏览器会提示“连接不安全”。此时不要怀疑服务器配置,先检查证书文件本身的完整性和匹配性。
7.3 数据库定时备份的兜底方案
无论你的项目多小,数据库备份这件事不能省。血的教训是:某开发者辛辛苦苦做了半年的项目,一次误操作把表删了,结果发现从没备份过,只能对着屏幕发呆。
一个最简单的定时备份方案,用crontab实现。先写一个备份脚本:
vim /opt/app/backup.sh#!/bin/bash BACKUP_DIR=/backup/mysql DATE=$(date +%F) mysqldump -u你的用户 -p你的密码 yourdb | gzip > $BACKUP_DIR/yourdb_$DATE.sql.gz find $BACKUP_DIR -type f -mtime +7 -exec rm -f {} \;给脚本加执行权限,加入定时任务:
chmod +x /opt/app/backup.sh crontab -e加入一行:
0 2 * * * /opt/app/backup.sh每天凌晨2点备份一次,保留7天的备份文件。
这里有个比较重要的经验:备份脚本执行前先用命令行手动跑一遍,确认备份文件能正常生成且可解压恢复。定时任务里环境变量和手动执行不同,如果脚本依赖某些环境变量(比如PATH),crontab环境里可能找不到命令,导致备份任务悄悄失败。脚本里用到mysqldump时,建议用绝对路径,或者脚本开头先source /etc/profile把环境导入。
7.4 磁盘与内存监控的几个命令
上线之后日常巡检必不可少,我常用的命令就这几个:
# 磁盘空间,重点看/根分区和/opt、/var分区是否快满 df -h # 内存使用,重点关注available是否充足 free -h # 负载情况,看CPU和内存占用最高的进程 top -c # IO情况(如果磁盘响应很慢) iostat -x 2 3把这些命令记在笔记里,每周跑一次,或者用云厂商自带的监控报警功能设置阈值。日志膨胀、内存泄漏、磁盘耗尽这些问题,越早发现处理成本越低。
写在最后的个人经验
整套流程走下来,其实难度不在于某个步骤有多复杂,而在于每个环节都可能埋着一个“看起来能用但其实不稳”的坑。我最想提醒你的一点是:部署完能访问只是一个开始,后面还要做进程守护、磁盘监控、备份策略,把这些做完才叫“部署完成”。
第一次部署的话,我建议不要跳步,把每一步的命令都理解后再执行。遇到报错就根据报错信息往上游查,比如启动失败先看日志、日志没输出就看进程状态、进程没起来就看JVM参数和JAVA_HOME,一层层查下去,问题基本都能定位。慢慢地你就有自己的排查套路了,那时候你会发现,部署这件事其实比写代码简单多了。