java -jar app.jar 在终端里跑得好好的,一关终端就断了——这是每个 Java 服务上线都要过的第一关。这篇从临时调试讲到生产托管,把三种方式和配套参数一起说清楚。
一、三种方式怎么选
| 方式 | 适合 | 断开终端 | 开机自启 | 崩溃自动拉起 |
|---|---|---|---|---|
前台 java -jar |
本地调试 | ❌ 进程被杀 | ❌ | ❌ |
nohup ... & |
临时跑、测试环境 | ✅ | ❌ | ❌ |
| systemd | 生产环境 | ✅ | ✅ | ✅ |
生产一律用 systemd,nohup 只是临时方案。
二、前台与后台(临时用)
java -jar app.jar # 前台,Ctrl+C 就停
nohup java -jar app.jar > app.log 2>&1 & # 后台,日志重定向到文件
echo $! > app.pid # 记下 PID
2>&1 不能省,否则 System.err 和未捕获异常的输出不会进日志文件,出问题查不到。
停掉:
kill $(cat app.pid) # 优雅停止(触发 shutdown hook)
kill -9 $(cat app.pid) # 强制,只在优雅停止无效时用
kill -9 的代价:Spring Boot 的 @PreDestroy 不执行、正在处理的请求直接断、临时文件不清理。默认的 kill 是 SIGTERM,Spring Boot 会走优雅关闭(配 server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s 效果更好)。
三、systemd 托管(生产标准)
新建 /etc/systemd/system/myapp.service:
[Unit]
Description=My Java App
After=network.target mysql.service
Wants=mysql.service
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -Xms512m -Xmx512m -jar /opt/myapp/app.jar --spring.profiles.active=prod
SuccessExitStatus=143
Restart=always
RestartSec=5
StandardOutput=append:/var/log/myapp/stdout.log
StandardError=append:/var/log/myapp/stderr.log
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
几个必写项:
User=appuser:别用 root 跑应用,这是安全底线。Restart=always:进程挂了自动拉起,这是 systemd 相对 nohup 最大的价值。SuccessExitStatus=143:Java 收到 SIGTERM 的退出码是 143,不写这行 systemd 会把正常停止标记成 failed。LimitNOFILE=65535:文件描述符上限,limits.conf 对 systemd 服务无效。
然后:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp # 开机自启 + 立即启动
sudo systemctl status myapp
journalctl -u myapp -f # 看日志
四、JVM 参数怎么给
-Xms512m -Xmx512m # 堆的初始与最大,设成一样避免动态扩容抖动
-XX:MaxMetaspaceSize=512m # 类元数据区,大量动态代理的服务必设
-XX:+UseG1GC # JDK 8 需显式指定;JDK 11+ 默认已是 G1
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/myapp/dump
内存预算(4GB 机器跑单 Java 服务的经验估算):堆 1–2G,元空间 512M,堆外(Netty 直接内存、线程栈、JNI)留 512M 以上,再给系统留 1G。堆不是越大越好——太大了 GC 停顿反而长。
容器里跑要加 -XX:+UseContainerSupport(JDK 8u191+ 默认开启),否则 JVM 看到的是宿主机内存,会超容器限额被 OOMKilled。
五、配置与日志
Spring Boot 指定配置三选一:
java -jar app.jar --spring.profiles.active=prod
java -jar app.jar --spring.config.location=/etc/myapp/application.yml
SPRING_CONFIG_LOCATION=/etc/myapp/ java -jar app.jar
外部配置文件优先级高于 jar 内:把 application.yml 放在 jar 同目录的 ./config/ 下会自动覆盖包内配置,改配置不用重新打包。
日志交给 logback/log4j2 自己滚动,别依赖重定向。app.log 无限增长的话配 logrotate:
/var/log/myapp/*.log {
daily
rotate 14
compress
missingok
copytruncate
dateext
}
六、8 个常见问题
| 现象 | 原因 | 解法 |
|---|---|---|
| 关终端进程就没了 | 用了前台启动 | 加 nohup 或改 systemd |
Port 8080 was already in use |
端口被占用 | ss -lntp | grep 8080 找 PID,或 --server.port=8081 |
nohup: 忽略输入并把输出追加到 nohup.out |
没重定向,日志全堆在 nohup.out | 显式 > app.log 2>&1 |
启动报 no main manifest attribute |
打的不是可执行 jar | 检查 spring-boot-maven-plugin 的 repackage |
| 改了配置不生效 | 改的是 jar 包内的配置 | 用外部 config 目录或启动参数覆盖 |
| 跑几天后变慢最后挂掉 | 内存泄漏 / 没设 -Xmx | 配 HeapDumpOnOutOfMemoryError,dump 用 MAT 分析 |
java: command not found |
systemd 不继承 PATH | ExecStart 写绝对路径 /usr/bin/java |
| 服务起来但访问不了 | 只监听 127.0.0.1 / 防火墙 | --server.address=0.0.0.0,放行端口 |
排查时把日志整段丢进 日志分析工具,端口占用、OOM、数据库连接失败这些会直接归到对应类型并给出处理建议。
相关工具:Linux 命令速查 · 日志分析 · HTTP 健康检查