跳到主要内容 java -jar 启动原理 | 极客日志
Java java
java -jar 启动原理 java -jar 命令用于运行可执行 JAR 包,依赖 JVM 类加载机制及 JAR 规范。核心前提是 JAR 包 META-INF/MANIFEST.MF 中必须声明 Main-Class 属性。启动流程包括解析参数、读取元数据、创建专属 JarClassLoader、加载主类并反射执行 main 方法。与普通 java 命令不同,java -jar 忽略系统 CLASSPATH,仅加载 JAR 内及 Class-Path 声明的依赖。常见错误为缺少 Main-Class 属性或格式错误。开发中可通过 Maven 或 Gradle 插件自动配置主类生成可执行 JAR。
深海蔚蓝 发布于 2026/3/15 更新于 2026/4/18 3 浏览java -jar 是 Java 中运行可执行 JAR 包(Executable JAR)的核心命令,其底层依托 JVM 类加载机制、JAR 包规范和程序入口约定实现,核心逻辑是「JVM 解析 JAR 包元数据→加载指定主类→执行入口方法」,以下从核心前提、完整启动流程、关键细节、与普通启动的区别四方面彻底讲清原理,附实操验证和注意事项。
一、核心前提:可执行 JAR 包的特殊要求
java -jar 能启动的前提是JAR 包必须是「可执行 JAR」 ,普通依赖 JAR 包(仅含 class 文件,无启动配置)无法通过该命令运行。可执行 JAR 包的核心标识是:在 META-INF/MANIFEST.MF 文件中声明主类入口 ,这是 JVM 识别「启动类」的唯一依据。
1. 可执行 JAR 包的 MANIFEST.MF 核心配置 该文件是 JAR 包的「元数据清单」,java -jar 启动时会优先读取其中的 Main-Class 属性,格式要求严格(冒号后必须有 1 个空格,结尾无多余空行 ),示例:
Manifest-Version: 1.0
Main-Class: com.example.DemoApplication # 核心:指定程序入口主类(含全类名)
Class-Path: lib/commons-lang3-3.14.0.jar lib/fastjson2-2.0.32.jar # 可选:依赖的外部 JAR 包路径
Main-Class:必填 ,指定包含 public static void main(String[] args) 方法的主类(全类名,无 .class 后缀);
Class-Path:可选 ,声明当前 JAR 包依赖的外部 JAR 包(相对路径,多个依赖用空格分隔,JVM 会按此加载依赖类);
格式要求:属性名大小写固定,冒号 : 后必须跟1 个空格 ,否则 JVM 会解析失败。
2. 普通 JAR 与可执行 JAR 的核心区别 类型 核心特征 启动方式 普通依赖 JAR 仅含 class 文件,无 Main-Class 无法 java -jar,仅作为其他项目的依赖被类加载 可执行 JAR 含 Main-Class 声明,有主入口 直接 java -jar xxx.jar 启动
二、java -jar xxx.jar 完整启动流程(JVM 视角) 当在命令行执行 java -jar 命令时,JVM 会按固定步骤 完成从「解析 JAR 包」到「执行 main 方法」的全过程,无额外手动干预,全程自动化,步骤如下(按执行顺序):
步骤 1:JVM 解析命令行参数,识别 -jar 标识 JVM 启动器(java.exe/java 可执行文件)首先解析命令行参数,当检测到 -jar 标识时,会触发 「JAR 包启动模式」 ,后续逻辑均围绕「加载并运行可执行 JAR 包」展开,同时忽略命令行中后续的其他类名参数(仅以 JAR 包为核心)。
步骤 2:加载并解析 JAR 包中的 META-INF/MANIFEST.MF 文件 JVM 会打开指定的 JAR 包(如 xxx.jar),按规范读取其中 META-INF 目录下的 MANIFEST.MF 元数据文件,核心做 2 件事:
校验文件格式是否合法(如是否有 Manifest-Version 基础属性);
提取 **「Main-Class 属性值」**(主类全类名),若未找到该属性,直接抛出异常:no main manifest attribute, in xxx.jar`(最常见启动失败原因)。
步骤 3:基于 JAR 包创建专属的「JAR 类加载器(JarClassLoader)」 Java 类加载遵循「双亲委派模型」,但 java -jar 启动时,JVM 会创建专属的 JarClassLoader (继承自 URLClassLoader),该类加载器的核心作用是:
以整个 JAR 包为「类加载源」,负责从 JAR 包的压缩结构中加载所有 class 文件(包括主类、业务类、内部依赖类);
若 MANIFEST.MF 中有 Class-Path 属性,JarClassLoader 会同时将该属性声明的外部 JAR 包加入类加载路径,按顺序加载外部依赖类。
核心特点:JarClassLoader 仅加载当前 JAR 包及 Class-Path 声明的依赖,与系统类加载器(AppClassLoader)隔离,保证启动环境的独立性。
步骤 4:通过 JarClassLoader 加载 Main-Class 指定的主类 JarClassLoader 会根据 Main-Class 的全类名,在 JAR 包中找到对应的 class 文件(如 com/example/DemoApplication.class,JAR 包中类的路径与包结构一致),并完成类的加载、链接、初始化 (类加载的三个阶段):
加载:将 class 文件的二进制数据读入内存,生成 Class 对象;
链接:验证(校验 class 文件合法性)→ 准备(为类变量分配内存)→ 解析(将符号引用转为直接引用);
初始化:执行类的静态代码块、初始化静态变量,完成类的初始化。
若主类加载失败(如类不存在、依赖缺失、class 文件损坏),会抛出 ClassNotFoundException 或 NoClassDefFoundError 异常,启动终止。
步骤 5:反射调用主类的 public static void main(String[] args) 方法 主类加载完成后,JVM 会通过Java 反射机制 定位主类中的 main 方法,核心要求是该方法必须满足固定签名 :
public static void main (String[] args)
JVM 会将命令行中 JAR 包后的参数(如 java -jar xxx.jar arg1 arg2)封装为 String[] 数组,传递给 main 方法并执行该方法 ,此时程序正式启动,进入业务逻辑执行阶段。
步骤 6:程序运行与 JVM 生命周期绑定 main 方法是程序的「入口主线程」,JVM 的生命周期会与该主线程绑定:
若主线程执行完毕(无后台线程 / 守护线程),JVM 会正常退出;
若主线程启动了其他用户线程(如业务线程、定时任务),JVM 会等待所有用户线程执行完毕后再退出;
若存在守护线程(如垃圾回收线程),守护线程会随 JVM 退出而终止。
三、关键细节与常见问题解析
1. 最常见启动失败:no main manifest attribute, in xxx.jar
原因 :JAR 包不是可执行 JAR,MANIFEST.MF 中缺失 Main-Class 属性,或属性格式错误(如冒号后无空格);
解决 :重新打包为可执行 JAR,确保 MANIFEST.MF 中正确声明 Main-Class(IDEA/Maven/Gradle 打包时可自动配置)。
2. Class-Path 属性的使用注意事项
路径类型:仅支持相对路径 (相对当前 JAR 包的运行目录),不支持绝对路径;
分隔符:多个依赖用空格 分隔(不是逗号),示例:Class-Path: lib/a.jar lib/b.jar;
加载规则:JarClassLoader 会按 Class-Path 顺序加载外部依赖,若依赖缺失,会抛出 ClassNotFoundException。
3. java -jar 与普通 java 主类名 启动的核心区别 很多人会混淆两种启动方式,核心差异在类加载器和类加载路径 ,具体对比:
启动方式 类加载器 类加载路径 适用场景 java -jar xxx.jarJarClassLoader 仅 JAR 包内 + Class-Path 生产环境独立运行可执行程序 java com.example.MainAppClassLoader 系统类路径(CLASSPATH) 开发环境快速运行,依赖已在 CLASSPATH 中
关键:java -jar 启动时,系统环境变量 CLASSPATH 会被忽略 ,所有依赖必须通过 JAR 包内或 Class-Path 声明,保证程序运行不依赖外部环境配置。
4. 命令行参数传递:java -jar xxx.jar arg1 arg2
传递规则:JAR 包后的所有参数会被封装为 String[],传递给主类的 main 方法;
示例:执行 java -jar demo.jar 8080 dev,则 main 方法的 args 数组为 {"8080", "dev"};
注意:-jar 前的参数是 JVM 参数(如 -Xmx512m、-Dspring.profiles.active=dev),后为程序参数,示例:
java -Xmx512m -Dspring.profiles.active=dev -jar demo.jar 8080
四、实操验证:手动查看可执行 JAR 包的 MANIFEST.MF 可通过解压工具(如 WinRAR/7-Zip)直接打开 JAR 包,进入 META-INF 目录,用记事本打开 MANIFEST.MF 文件,验证是否有 Main-Class 属性,步骤:
找到可执行 JAR 包(如 demo.jar),右键选择「解压到当前文件夹」;
进入解压后的 META-INF 目录,打开 MANIFEST.MF;
检查是否有 Main-Class: 全类名 配置,格式是否正确(冒号后有空格)。
五、扩展:Maven/Gradle 打包可执行 JAR 包的原理 日常开发中,我们不会手动编写 MANIFEST.MF,而是通过 Maven(maven-jar-plugin/spring-boot-maven-plugin)或 Gradle 插件自动打包,核心原理是:
插件在打包时,会根据配置的「主类全类名」,自动生成 MANIFEST.MF 文件并写入 Main-Class 属性;
Spring Boot 可执行 JAR 包特殊:会生成嵌套 JAR 结构,同时声明 Spring-Boot-Main-Class 和 Main-Class(Main-Class 为 Spring Boot 启动器 org.springframework.boot.loader.JarLauncher,再由其加载实际业务主类)。
六、核心总结
java -jar 启动的前提 是 JAR 包为「可执行 JAR」,核心标识是 META-INF/MANIFEST.MF 中声明 Main-Class;
完整启动流程:解析 -jar 标识→读取 MANIFEST.MF→创建 JarClassLoader→加载主类→反射执行 main 方法 ;
JarClassLoader 是专属类加载器,仅加载 JAR 包内及 Class-Path 声明的依赖,忽略系统 CLASSPATH ;
最常见失败原因是缺失 Main-Class 或格式错误,解决方式是重新打包并正确配置主类;
-jar 前为 JVM 参数(如 -Xmx、-D),后为程序参数,会传递给 main 方法的 args 数组。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online