java文件的编译过程
编译过程

-
第一步:把.java源文件编译为.class字节码文件(也叫jvm指令集),与平台无关,这是第一次编译,生成的这个文件就是可到处运行的文件,流程如下图
-

-
第二步:.class字节码文件到目标机器码,这个过程由JVM执行引擎来完成,是第二次编译。流程如下图
-

java程序能够一次编译到处执行的原因
- 第二次编译是在JVM中执行的,而JVM适配了不同的操作系统,根据不同的操作系统解释成不同的机器码
- 不同平台对应不同JVM,JVM会根据平台生成不同指令集
| 平台 | 安装的 JVM | 生成的机器码 |
|---|---|---|
| Windows x64 | HotSpot for Windows | x86-64 指令 |
| Linux x64 | HotSpot for Linux | x86-64 指令 |
| macOS ARM | HotSpot for macOS | ARM64 指令 |
| Android | ART / Dalvik | ARM 指令 |
执行引擎:解释执行 vs JIT 编译
- 首先,java是解释机制,不是编译机制,虽然java经过了两次编译,但是第二次编译是在解释适应不同平台,在第二次编译过程中,真正的采用解释机制,即翻译一句,执行一句,不产生整个的机器代码,翻译过程如果不报错,会一直执行
- 但同一个程序,解释执行比编译执行的运行速度较慢,但对于java来说差别不大,在了解其原因前先了解下JVM 执行字节码的两种方式
1.解释执行
sql
字节码 ──► 解释器逐条读取 ──► 翻译成机器码 ──► 立即执行
- 优点:无需等待编译,直接解释成机器码,启动快
- 缺点:每条指令都要翻译,执行慢
JIT即时编译(just-in-time complier)
sql
热点代码(执行次数超过阈值)
│
▼
┌─────────────┐
│ C1 编译器 │ ← 客户端编译器,优化简单,编译快
│ (-client) │ 适合桌面应用
└─────────────┘
│
▼
┌─────────────┐
│ C2 编译器 │ ← 服务端编译器,深度优化,编译慢但执行快
│ (-server) │ 方法内联、逃逸分析、锁消除等
│ (Graal JIT)│
└─────────────┘
│
▼
本地机器码(缓存到 Code Cache)
执行流程图
sql
┌─────────────────┐
│ 字节码文件 │
└─────────────────┘
│
▼
┌─────────────────┐
│ 解释器直接执行 │ ◄──── 冷代码(执行次数少)
│ (启动阶段) │
└─────────────────┘
│
▼ 执行次数达到阈值
┌─────────────────┐
│ JIT 编译器介入 │
│ - C1 简单优化 │
│ - C2 深度优化 │
└─────────────────┘
│
▼
┌─────────────────┐
│ 生成本地机器码 │
│ 存入 Code Cache │
└─────────────────┘
│
▼
┌─────────────────┐
│ 后续直接执行机器码 │ ◄──── 热点代码(执行次数多)
│ (不再解释字节码) │
└─────────────────┘
这就是java虽然是解释执行,但相比编译执行的运行速度不差多少,就是因为JVM有JIT机制和解释执行两种方式配合执行,JIT适合热点代码,执行快;解释执行适合冷代码,启动快,两者相互搭配。
JIT 编译是 Java 性能接近 C++ 的关键。它甚至能根据运行时信息做优化(如方法内联、逃逸分析),这是静态编译难以做到的。
且对于Java项目来说,当服务器持续处理请求时,JIT会持续优化,当热点代码被JIT 编译后,执行速度很快。
所以,Java服务需要预热
JDK JVM JRE IDE之间的关系
关系图
sql
┌─────────────────────────────────────────┐
│ JDK (Java Development Kit) │ ← 开发工具包
│ ┌─────────────────────────────────┐ │
│ │ JRE (Java Runtime Environment) │ ← 运行时环境
│ │ ┌─────────────────────────────┐ │ │
│ │ │ JVM (Java Virtual Machine) │ │ ← 虚拟机核心
│ │ │ ┌─────────────────────┐ │ │ │
│ │ │ │ 核心类库 (rt.jar) │ │ │ │
│ │ │ │ (String, List, IO) │ │ │ │
│ │ │ └─────────────────────┘ │ │ │
│ │ │ ┌─────────────────────┐ │ │ │
│ │ │ │ 运行辅助工具 │ │ │ │
│ │ │ │ (java, javaw) │ │ │ │
│ │ │ └─────────────────────┘ │ │ │
│ │ └─────────────────────────────┘ │ │
│ │ ┌─────────────────────────────────┐ │
│ │ │ 扩展类库 (ext/) │ │
│ │ └─────────────────────────────────┘ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ 开发工具 │ │
│ │ javac, javadoc, jar, jdb, javap │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
│
▼
┌─────────────┐
│ IDE │ ← 集成开发环境
│ (IntelliJ IDEA│ 代码编辑器 + 构建工具 + 调试器 + ...
│ Eclipse, │
│ VS Code) │
└─────────────┘
详细对比
| 组件 | 全称 | 作用 | 包含关系 |
|---|---|---|---|
| JDK | Java Development Kit | 开发 + 运行 Java 程序的完整工具包 | 包含 JRE + 开发工具 |
| JRE | Java Runtime Environment | 运行 Java 程序的环境(不含编译器) | 包含 JVM + 核心类库 |
| JVM | Java Virtual Machine | 执行字节码的虚拟机,内存管理、GC、线程调度 | JRE 的核心 |
| IDE | Integrated Development Environment | 集成开发环境,提供代码编辑、调试、构建、版本控制等 | 独立于 JDK,但依赖 JDK |
关键结论
| 问题 | 答案 |
|---|---|
| 每次请求都重新编译吗? | 否。类只加载一次,字节码只编译一次(JIT 缓存机器码) |
| 每次请求都重新类加载吗? | 否。类加载在首次使用时完成,后续复用 |
| 多次请求 JVM 在做什么? | 复用已有的类、方法、对象;线程从池里取;JIT 持续优化热点代码 |
| 请求越多性能越好? | 是。热点方法被 JIT 编译后,执行速度接近 C++ |
springboot项目从启动到运行的大致流程
sql
┌─────────────────────────────────────────────────────────────┐
│ 构建阶段(Build Time)------ 编译成 .class │
│ 执行者:Maven / Gradle + javac │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. mvn clean package 或 ./gradlew build │
│ │
│ └─► 扫描 src/main/java 下的所有 .java 文件 │
│ │
│ └─► 调用 javac 编译 │
│ UserController.java ──► UserController.class │
│ UserService.java ──► UserService.class │
│ ... │
│ │
│ └─► 编译后的 .class 文件输出到 │
│ target/classes/ (Maven) │
│ build/classes/ (Gradle) │
│ │
│ └─► 打包成可执行 JAR │
│ target/app-1.0.jar │
│ 结构: │
│ ├─ BOOT-INF/classes/ ← 业务 .class 文件 │
│ ├─ BOOT-INF/lib/ ← 依赖 JAR(Spring、Tomcat等)│
│ └─ META-INF/MANIFEST.MF ← 主类入口 │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 启动阶段(Startup)------ JVM 加载运行 │
│ 执行者:java -jar app.jar │
├─────────────────────────────────────────────────────────────┤
│ │
│ 2. java -jar app.jar │
│ │
│ └─► JVM 启动 │
│ ├─ 加载核心类库(rt.jar / modules) │
│ ├─ 创建 Bootstrap / Ext / App ClassLoader │
│ └─ 找到 MANIFEST.MF 中的 Main-Class │
│ │
│ └─► SpringApplication.run() 执行 │
│ ├─ 创建 Spring 上下文 │
│ ├─ 扫描并加载 BOOT-INF/classes/ 下的 .class │
│ ├─ 从 BOOT-INF/lib/ 加载依赖 JAR 中的 .class │
│ ├─ 实例化 Bean、依赖注入 │
│ └─ 启动内嵌 Tomcat(创建非守护线程) │
│ │
│ 3. 类加载器工作(此时加载的是已编译好的 .class) │
│ └─► AppClassLoader / LaunchedURLClassLoader │
│ 从 JAR 包内读取 .class 字节码 → 加载到方法区 │
│ │
│ 4. JIT 编译(运行时才发生) │
│ └─► 解释执行字节码 → 热点检测 → C1/C2 编译为机器码 │
│ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 运行阶段(Runtime)------ 处理请求 │
│ 执行者:JVM + Spring + Tomcat │
├─────────────────────────────────────────────────────────────┤
│ │
│ 5. 请求到来 → Worker 线程从线程池取出 │
│ └─► 执行 Controller 方法(已加载的 .class 字节码) │
│ └─► 热点方法被 JIT 编译后直接执行机器码 │
│ │
└─────────────────────────────────────────────────────────────┘