Java启动命令-jar和-cp有什么区别?
java -cp
语法
java -cp <classpath> <main-class> [args...]
classpath 的格式:
Windows:英文分号 ;分隔Linux/macOS:冒号 :分隔支持 *通配符:通过lib/*匹配所有jar包
核心流程
第一步:通过sun.misc.Launcher读取-cp参数
第二步:将路径字符串按分隔符拆分,生成File[]并转为URL[]
第三步:创建URLClassLoader,设置URLClassPath
第四步:AppClassLoader.loadClass("com.example.App")在URLClassPath中搜索.class文件
第五步:通过defineClass()验证→准备→解析→初始化类实例
第六步:反射调用main(String[])方法
特性
能够完全控制classpath路径,精确指定哪些目录/jar参与类加载 忽略jar包内的 MANIFEST.MF声明通配符 *在JVM层面展开
脚本示例
#!/bin/bash# Linux启动脚本start.sh# 适用场景:依赖多个外部jar,需要灵活指定classpath# 配置APP_NAME="my-service"BASE_DIR="/opt/app/${APP_NAME}"MAIN_CLASS="com.example.Bootstrap"JAVA_OPTS="-Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"# 构建classpath(含lib目录下所有jar和config目录)CLASSPATH="${BASE_DIR}/config"for jar in "${BASE_DIR}/lib"/*.jar; do CLASSPATH="${CLASSPATH}:${jar}"done# 启动exec java ${JAVA_OPTS} \ -cp "${CLASSPATH}" \ -Dapp.name="${APP_NAME}" \ -Dlogging.config="${BASE_DIR}/config/logback.xml" \ ${MAIN_CLASS} \ "$@"
java -jar
语法
java -jar app.jar [args...]
核心流程
Manifest文件声明示例
Manifest-Version: 1.0Main-Class: com.example.BootstrapClass-Path: lib/spring-core.jar lib/spring-context.jar lib/logback.jar
JVM规范约束
根据JLS§12.1.1和JVM规范§5.2中规定,在java -jar模式下需满足以下约束:
-cp/-classpath参数将被IGNORED(强制忽略) CLASSPATH环境变量将被IGNORED (强制忽略) 唯一classpath来源等于jar包本身 + MANIFEST.MF的Class-Path声明的路径 如果MANIFEST.MF缺少Main-Class属性则抛出错误
脚本示例
#!/bin/bash# Linux启动脚本start.sh# 适用场景:SpringBoot fat jar部署APP_NAME="my-boot-app"JAR_PATH="/opt/app/${APP_NAME}/${APP_NAME}.jar"JAVA_OPTS=" -Xms2g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/app/${APP_NAME}/dump/ -Dspring.profiles.active=prod -Dcom.sun.management.jmxremote"# 检查 jar 是否存在if [ ! -f "${JAR_PATH}" ]; then echo "ERROR: ${JAR_PATH} not found!" exit 1fi# 启动exec java ${JAVA_OPTS} \ -jar "${JAR_PATH}" \ "$@"
适用场景
在实际应用场景中java -cp和java -jar该如何选择?它们分别使用哪些场景中呢?
-cp的适用场景
微服务多模块:各模块独立jar包,启动时按需组合 开发/调试:快速切换主类,不需要重新打包 工具类/CLI:依赖外部jar包的动态变化 自定义ClassLoader:需要精准控制类加载顺序
-jar的适用场景
Spring Boot部署:fat jar约定优于配置 容器化(Docker):单一交付物,镜像简洁 CI/CD发布:版本管理简单,一个jar包一个版本 运维友好:非技术人员只需一个命令
注意事项
-jar命令中-cp命令将会被忽略
# ❌错误:-cp被忽略,Main-Class不会在my-utils.jar中搜索java -jar app.jar -cp my-utils.jar# ✅正确:Class-Path写在MANIFEST.MF声明中# 或在jar外层用-jar依赖其他jar时,使用-Xbootclasspath/a或扩展类路径
-cp命令中MANIFEST.MF文件声明不生效
# -cp不会读取MANIFEST.MF中的Main-Classjava -cp app.jar # 会报错 "找不到或无法加载主类"# 必须显式指定java -cp app.jar com.example.Main
通配符使用
# ✅正确使用方式java -cp "lib/*:config" com.example.App# ❌错误:引号缺失,shell展开*可能会有问题java -cp lib/*:config com.example.App
总结
一句话总结:java -cp是我告诉你主类在哪,也告诉你依赖在哪,而java -jar则是约定优于配置。java -jar方式是从MANIFEST.MF声明中获取启动信息,java -cp方式则从命令行取相关信息,但两条两种方式最终都是通过URLClassLoader.loadClass()来解析和加载相关类的。
