jar包是Java生态中最常见的交付物形式,而SpringBoot项目打包之后通常也以一个可执行jar的形态部署到服务器上。看似只要敲一行java -jar就能跑起来,但实际操作中隐藏着不少细节:有人把war包和jar包混为一谈,有人后台启动后找不到日志,有人改了MANIFEST.MF导致类找不到。这篇文章就把jar命令本身的用法和SpringBoot通过java命令启动的完整链路讲清楚,顺便把常见的坑挨个列出来。

一、jar命令的基础用法你真的掌握了吗
jar命令随JDK一起发布,位于JDK的bin目录下,本质上是基于zip格式加上一份清单文件(MANIFEST.MF)构成的压缩包。很多人只会在IDE里点一下打包按钮,一旦脱离IDE就不知道怎么手动操作,这在排查线上问题时会很被动。先看几个最常用的命令组合。
# 查看 jar 包内容(不解压,只列出文件清单) jar tf app.jar # 解压 jar 包到当前目录 jar xf app.jar # 把 class 目录打成普通 jar 包 jar cf mylib.jar -C classes . # 创建可执行 jar,指定主类 jar cfe app.jar com.example.Main -C classes .
其中-C classes .表示切换到classes目录再打包当前目录下的所有内容,这个参数在打多模块依赖时非常实用。参数含义总结一下:c代表创建,x代表解压,t代表列出内容,f指定文件名,e指定程序入口主类,v显示详细过程。需要注意的是,jar命令的参数顺序和后面的文件名是对应的,比如jar cfe app.jar com.example.Main中,f后面的第一个值就是输出文件名,e后面的值就是主类全限定名,顺序一旦写错,打出来的包结构就不对。
另外一个容易忽略的点:jar本质是zip,所以用unzip -l app.jar或者压缩工具也能打开查看,但不要用普通解压工具直接修改后再压缩回去,因为清单文件的存储位置和编码有讲究,手工压缩很容易破坏掉META-INF目录结构,导致原本可执行的jar变成普通压缩包。
二、SpringBoot通过java命令启动的底层机制
SpringBoot的可执行jar和普通jar结构差别很大。普通jar的类都放在根目录下,而SpringBoot的可执行jar把业务代码放在BOOT-INF/classes目录,依赖的第三方库放在BOOT-INF/lib目录,真正的入口类是MANIFEST.MF中指定的org.springframework.boot.loader.JarLauncher(新版本是org.springframework.boot.loader.launch.JarLauncher)。启动流程是java命令先加载Launcher,Launcher再创建自定义类加载器去加载BOOT-INF下的内容和依赖,最后反射调用你写的main方法。
# 前台启动(退出终端进程就停了) java -jar app.jar # 指定激活的配置文件和环境变量启动 java -jar app.jar --spring.profiles.active=prod # 通过 JVM 参数和系统属性启动 java -Xms512m -Xmx1024m -jar app.jar -Dserver.port=8081
这里有个参数顺序的经典坑:-Xms、-Xmx这类JVM参数必须写在-jar之前,而-Dserver.port=8081这样的系统属性如果放在jar包名之后,会被当作应用参数传递给main方法的args数组,SpringBoot能识别但优先级机制不同。上面第三条命令里-Dserver.port=8081放在了jar名后面,严格来说它不会作为JVM系统属性生效,正确的写法是放在-jar app.jar之前。这类细节在排查“为什么端口没改过来”时非常关键。
还要理解嵌套jar的加载原理。SpringBoot的类加载器支持直接从jar内的lib目录读取嵌套的jar文件,这是它魔改过的实现,普通的java -cp方式是无法直接加载嵌套jar的。所以如果你试图把SpringBoot的fat jar用java -cp app.jar com.example.Main来启动,大概率会报ClassNotFoundException,正确的替代方案是解压后启动,或者使用SpringBoot提供的PropertiesLauncher。
三、高频踩坑点与排查思路
第一类坑是后台运行问题。直接java -jar app.jar后关闭终端,进程会随会话一起被杀掉。常见的处理方式有三种:nohup配合输出重定向、setsid脱离会话、或者注册成systemd服务。推荐用systemd管理,能获得开机自启和崩溃自动拉起的能力。
# 后台运行并记录日志 nohup java -jar app.jar > app.log 2>&1 & # 查看进程是否存活 ps -ef | grep app.jar | grep -v grep # 查看端口占用情况 netstat -tlnp | grep 8080 lsof -i :8080
第二类坑是路径和编码问题。如果jar包放在带空格的目录下(比如Windows的Program Files),启动命令必须给路径加引号,否则会被截断。Linux下文件名带中文也可能因为终端编码不一致导致找不到文件。第三类坑是JDK版本不匹配,报UnsupportedClassVersionError说明编译时用的JDK版本比运行时的高,用java -version确认服务器环境即可。第四类是内存问题,容器化部署时没有设置合理的堆大小,宿主机内存一紧张容器被OOM Killer杀掉,进程悄无声息消失,这时要看系统日志/var/log/messages而不是应用日志。
最后整理一张常见问题速查表:
| 现象 | 可能原因 | 解决方式 |
|---|---|---|
| 启动报找不到或无法加载主类 | MANIFEST.MF被修改或包损坏 | 重新打包,检查Main-Class配置 |
| 端口冲突启动失败 | 8080被其他进程占用 | 换端口或杀掉占用进程 |
| 改了配置不生效 | 参数放在了jar名之后 | JVM参数统一放在-jar之前 |
| 进程莫名退出 | OOM或nohup方式不对 | 调整内存参数,用systemd托管 |
四、生产环境启动脚本的推荐写法
把上面的避坑点整合起来,一个稳妥的启动脚本大致如下,包含了日志目录管理、GC日志、堆内存设置和优雅停机配置。
#!/bin/bash APP_NAME=app JAR_FILE=/opt/app/app.jar LOG_DIR=/opt/app/logs mkdir -p $LOG_DIR nohup java -Xms1g -Xmx1g \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=$LOG_DIR \ -Xlog:gc*:$LOG_DIR/gc.log:time,uptime \ -Dserver.port=8080 \ -Dspring.profiles.active=prod \ -jar $JAR_FILE > $LOG_DIR/stdout.log 2>&1 & echo "started, pid: $!"
注意脚本里续行符反斜杠后面不能有任何空格,这是shell脚本里非常经典的错误。优雅停机建议配合server.shutdown=graceful配置使用,先停止接收新请求,处理完存量请求再退出。掌握了jar命令的操作、启动参数的位置规则以及后台运行的正确姿势,绝大多数SpringBoot部署问题都能在几分钟内定位到原因。
jar命令SpringBoot启动jar包运行修改时间:2026-09-16 01:48:35