引言
为什么要学习这个主题?
你是否曾经好奇:
- 为什么Java程序能够"一次编写,到处运行"?
- 一个简单的
System.out.println("Hello World")背后发生了什么? .java文件和.class文件有什么区别?
作为JVM学习的第一课,理解Java程序的运行机制是后续所有知识的基础。就像学习汽车驾驶前需要了解汽车的基本构造一样,学习JVM调优前也需要先理解Java程序是如何运行的。
你将学到什么?
- ✅ Java程序从源代码到执行的完整流程
- ✅ JVM在Java运行机制中的核心作用
- ✅ “一次编写,到处运行"的底层原理
- ✅ 编译型语言 vs 解释型语言,Java是什么?
一、从HelloWorld开始
1.1 最简单的Java程序
// HelloWorld.java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
执行这个程序需要两个步骤:
# 第一步:编译
javac HelloWorld.java
# 第二步:运行
java HelloWorld
输出:
Hello, World!
看起来很简单,但这两行命令背后隐藏着Java运行机制的全部秘密。
1.2 编译后发生了什么?
执行 javac HelloWorld.java 后,目录中会生成一个新文件:
HelloWorld.java # 源代码(Java语言)
HelloWorld.class # 字节码(JVM语言)
关键问题:为什么不是直接生成机器码(如 .exe 文件)?
这就是Java与C/C++等传统编译型语言的核心区别。
二、Java程序的两阶段运行模型
2.1 传统编译型语言(如C语言)
源代码(.c) → [C编译器] → 机器码(.exe) → [CPU直接执行]
特点:
- 编译后生成特定平台的机器码
- Windows编译的程序不能在Linux上运行
- 执行速度快(直接被CPU执行)
- 无法跨平台
2.2 Java的两阶段模型
源代码(.java) → [javac编译器] → 字节码(.class) → [JVM解释/编译] → 机器码 → [CPU执行]
特点:
- 第一阶段:编译成平台无关的字节码
- 第二阶段:JVM将字节码转换为特定平台的机器码
- 关键:字节码可以在任何安装了JVM的平台上运行
2.3 流程图
┌─────────────────┐
│ HelloWorld.java │ ← Java源代码(人类可读)
└────────┬────────┘
│ javac编译器
↓
┌─────────────────┐
│ HelloWorld.class│ ← 字节码(JVM可读)
└────────┬────────┘
│ 运行时(java命令)
↓
┌─────────────────┐
│ JVM │ ← Java虚拟机(核心角色)
│ ┌───────────┐ │
│ │类加载器 │ │ ← 加载.class文件
│ ├───────────┤ │
│ │字节码校验 │ │ ← 安全检查
│ ├───────────┤ │
│ │解释器/JIT │ │ ← 转换为机器码
│ └───────────┘ │
└────────┬────────┘
│
↓
┌─────────────────┐
│ 操作系统 │
│ CPU执行 │
└─────────────────┘
三、核心概念深度解析
3.1 什么是字节码?
字节码(Bytecode):一种中间表示形式,既不是源代码,也不是机器码。
查看字节码内容:
javap -c HelloWorld.class
输出示例:
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, World!
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
这些类似 getstatic、ldc、invokevirtual 的指令就是JVM指令,是JVM能够理解的"语言”。
为什么需要字节码?
- 平台无关性:同一份字节码可以在Windows、Linux、macOS上运行
- 安全性:JVM可以在加载时验证字节码的合法性
- 优化空间:JVM可以在运行时对热点代码进行优化(JIT编译)
3.2 JVM的核心作用
JVM就像是Java程序和操作系统之间的"翻译官":
Java程序:老板,我要在屏幕上打印"Hello World"
↓
JVM:收到!让我看看...
→ Windows系统:调用 kernel32.dll
→ Linux系统:调用 glibc
→ macOS系统:调用 CoreFoundation
核心职责:
- 加载字节码:读取
.class文件 - 校验字节码:确保代码安全(防止恶意代码)
- 执行字节码:
- 解释执行(逐行翻译成机器码)
- JIT编译(将热点代码编译成机器码缓存)
- 内存管理:自动分配和回收内存(垃圾回收)
3.3 “一次编写,到处运行"的实现
同一份字节码
↓
┌───────┴───────┐
↓ ↓
Windows JVM Linux JVM
↓ ↓
Windows机器码 Linux机器码
↓ ↓
Windows CPU Linux CPU
关键点:
- 开发者只需编译一次(javac)
- JVM负责适配不同平台
- 开发者无需关心底层操作系统和CPU架构
四、Java是编译型还是解释型语言?
4.1 传统定义
- 编译型语言:C、C++、Go(编译成机器码)
- 解释型语言:Python、JavaScript(逐行解释执行)
4.2 Java的特殊性
Java是两者的结合:
编译阶段(javac):Java源码 → 字节码(编译型)
运行阶段(JVM): 字节码 → 机器码(解释型 + JIT编译)
更准确的描述:Java是一种混合型语言
- 第一阶段:编译成字节码(类似编译型)
- 第二阶段:
- 初期:解释执行字节码(类似解释型)
- 后期:JIT编译热点代码(变成编译型)
4.3 性能对比
纯解释执行 < Java(解释+JIT) < C/C++(纯编译)
慢 中等 快
实际情况:
- 启动时:Java较慢(需要加载JVM和类)
- 预热后:Java接近C++性能(JIT编译优化)
五、实战:观察Java程序的执行过程
5.1 查看编译产物
# 编译
javac HelloWorld.java
# 查看文件大小
ls -lh HelloWorld.*
输出:
-rw-r--r-- 1 user staff 119B Nov 21 20:00 HelloWorld.java
-rw-r--r-- 1 user staff 426B Nov 21 20:01 HelloWorld.class
分析:字节码文件比源代码还大,因为包含了:
- 常量池
- 方法表
- 字节码指令
- 元数据信息
5.2 查看JVM进程
终端1:运行一个持续运行的程序
public class LoopTest {
public static void main(String[] args) throws Exception {
while (true) {
System.out.println("Running...");
Thread.sleep(1000);
}
}
}
终端2:查看JVM进程
jps -l
输出:
12345 LoopTest
12346 sun.tools.jps.Jps
分析:
- 每个Java程序都运行在一个JVM进程中
jps是JDK自带的工具(Java Process Status)
六、常见误区与疑问
❓ 误区1:Java程序是解释执行的,所以很慢
真相:
- 早期的JVM确实主要依赖解释执行,性能较差
- 现代JVM(如HotSpot)拥有强大的JIT编译器
- 热点代码会被编译成本地机器码,性能接近C++
- 结论:现代Java的性能已经不是问题
❓ 误区2:JVM只能运行Java语言
真相:
- JVM运行的是字节码,而不是Java源代码
- 任何能编译成字节码的语言都能在JVM上运行
- JVM语言家族:
- Java(主流)
- Kotlin(Android开发)
- Scala(大数据)
- Groovy(动态语言)
- Clojure(函数式编程)
❓ 疑问3:为什么不直接编译成机器码?
原因:
- 跨平台性:字节码在所有平台上相同
- 安全性:JVM可以在加载时验证代码
- 动态性:支持动态类加载、反射等特性
- 优化空间:JIT可以根据运行时情况优化
代价:
- 启动较慢(需要加载JVM)
- 内存占用较高(JVM本身需要内存)
总结
核心要点回顾
Java程序运行的两阶段模型
- 编译阶段:
javac将.java编译成.class(字节码) - 运行阶段:
java命令启动JVM,JVM加载并执行字节码
- 编译阶段:
JVM的核心作用
- 加载和校验字节码
- 将字节码转换为机器码(解释执行 + JIT编译)
- 提供内存管理和垃圾回收
“一次编译,到处运行"的实现
- 字节码是平台无关的中间表示
- 不同平台的JVM负责将字节码转换为对应的机器码
Java的语言特性
- 既不是纯编译型,也不是纯解释型
- 是编译型和解释型的混合体
- 现代JVM的性能已接近传统编译型语言
思考题
- 如果删除
.java源文件,只保留.class文件,程序还能运行吗?为什么? - 一个32位系统上编译的
.class文件能在64位系统上运行吗? - 为什么有些Java程序启动很慢,但运行一段时间后变快了?
下一篇预告
下一篇文章《JVM到底是什么?虚拟机的本质》,我们将深入探讨:
- 什么是虚拟机?
- JVM规范 vs JVM实现
- 主流JVM对比:HotSpot、OpenJ9、GraalVM
- JVM的内存模型和组件架构
参考资料
- Java Language and Virtual Machine Specifications
- 《深入理解Java虚拟机(第3版)》- 周志明,第1章
- The Java Virtual Machine Instruction Set
- Inside the Java Virtual Machine - Bill Venners
欢迎在评论区分享你的学习心得或提出疑问!