Java 启动 jar 包:nohup、systemd、JVM 参数与优雅停止

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 健康检查

还有 65 个免费在线工具

纯前端实现,不用注册,数据不上传服务器。

浏览全部工具