JDK、JRE、JVM 与跨平台原理 | JavaSE
JDK、JRE、JVM 与跨平台原理
一、学习目标
完成本章后,你应该能够:
- 能够准确解释 JDK、JRE、JVM 和 Java 标准类库分别负责什么。
- 能够区分“传统 JDK/JRE 包含关系”与 JDK 21 的现代运行时结构。
- 能够描述 Java 源代码从
.java文件到最终执行的大致过程。 - 能够解释
.class文件和 Java 字节码(Bytecode)的作用。 - 能够解释 Java 跨平台真正依赖的机制,而不是只背“一次编译,到处运行”。
- 能够判断为什么 JVM 本身需要区分 Windows、Linux、macOS 和不同 CPU 架构。
- 能够理解 JVM 是一种规范,而 HotSpot 等是具体实现。
二、核心知识
2.1 先看整个 Java 程序运行链路
在深入 JDK、JRE、JVM 之前,先建立整个流程。
假设我们有:
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
源文件叫:
HelloJava.java
最经典的 Java 编译运行过程是:
HelloJava.java
│
│ javac 编译
▼
HelloJava.class
│
│ JVM 加载并执行
▼
Java Virtual Machine
│
│ 解释 / JIT 编译等
▼
操作系统 + CPU
这条链路中出现了几个非常重要的角色:
JDK
JRE
JVM
Java Class Library
javac
java
.class
Bytecode
接下来逐个拆开。
2.2 什么是 JDK
JDK:
Java Development Kit
中文通常称为:
Java 开发工具包
它面向的是:
Java 开发者。
开发 Java 程序不仅需要运行程序,还需要:
- 编译;
- 运行;
- 生成文档;
- 调试;
- 分析;
- 打包;
- 诊断 JVM。
因此 JDK 不只是一个 java.exe。
它是一整套 Java 开发环境和工具集合。
JDK 21 中包含大量工具,例如:
java
javac
javadoc
jar
jshell
jlink
jpackage
jcmd
jstack
jmap
jps
...
目前只需要重点认识两个:
javac
Java 编译器。
主要负责:
.java
↓
.class
java
Java 应用程序启动器。
主要负责启动 Java 运行环境并执行 Java 程序。
因此从初学者角度可以先记:
要开发 Java 程序,安装 JDK。
2.3 什么是 JVM
JVM:
Java Virtual Machine
中文:
Java 虚拟机
JVM 是整个 Java 技术体系中非常核心的一层。
可以把真实计算机简化成:
CPU
内存
指令系统
运行时环境
而 Java 又定义了一台“抽象计算机”:
Java Virtual Machine
Java 编译器产生的代码并不是简单针对:
Intel CPU
AMD CPU
ARM CPU
Windows
Linux
macOS
中的某一个平台生成。
它主要生成 JVM 能够理解的:
Java 字节码(Java Bytecode)
字节码通常存放在:
.class
文件中。
2.4 JVM 是真实存在的机器吗
不是一台物理机器。
“Virtual Machine”中的“Virtual”就是:
虚拟的、抽象的。
JVM 定义:
- 能理解什么指令;
- class 文件应该是什么格式;
- 有哪些运行时数据区域;
- 类怎样加载;
- 字节码如何执行;
- 方法调用如何表示;
- 对象和类型怎样工作。
真实计算机厂商制造的是:
x86 CPU
ARM CPU
而 Java 世界的软件实现负责:
在真实硬件和操作系统之上实现 JVM 所规定的行为。
所以可以理解成:
Java 程序
│
▼
JVM
│
▼
操作系统
│
▼
CPU
2.5 JVM 规范与 JVM 实现
这是一个非常重要的专业区别。
JVM 规范
Java Virtual Machine Specification 规定:
一台符合 Java 标准的虚拟机应该表现成什么样。
它是一套规范。
JVM 实现
真正能够运行程序的 JVM 软件,是规范的具体实现。
例如我们学习 Oracle/OpenJDK JDK 时,经常会接触:
HotSpot JVM
所以:
JVM
并不严格等于:
HotSpot
而应该理解为:
JVM Specification
│
├── HotSpot
├── 其他 JVM 实现
└── ...
类似于:
“汽车设计规范”不是某一辆具体汽车。
规范描述规则,实现负责真正运行。
2.6 什么是 JRE
JRE:
Java Runtime Environment
中文:
Java 运行环境
在传统 Java 教学中,经常使用下面这个经典模型:
JDK
│
├── 开发工具
│ ├── javac
│ ├── javadoc
│ └── ...
│
└── JRE
│
├── JVM
└── Java 类库与运行组件
于是得到经典关系:
JDK = JRE + 开发工具
JRE = JVM + Java 类库 + 运行组件
这个模型非常适合理解三者的职责:
- JDK:开发;
- JRE:运行;
- JVM:执行字节码。
但是到了现代 Java,必须增加一个重要说明。
2.7 JDK 21 中应该怎样理解 JRE
如果你学习的是老版本 Java,尤其是 JDK 8,经常能看到这样的安装目录:
jdk/
└── jre/
于是非常容易产生一种印象:
JDK 里面永远存在一个叫
jre的目录。
到了现代模块化 JDK,这种理解已经不准确。
从 JDK 9 开始,Java 运行时结构发生了较大变化。
到了 JDK 11:
JDK 安装目录已经不再包含过去那种独立的 JRE image。
因此在 JDK 21 教学基线下,不应该把:
JDK > JRE > JVM
机械理解成三个嵌套文件夹。
更准确的理解是职责层次:
开发能力
│
▼
JDK
│
├── 编译、调试、诊断等开发工具
│
└── 运行 Java 程序所需能力
│
├── JVM
└── Java 标准模块 / 类库 / 运行组件
也就是说:
“JRE”仍然可以作为“Java 运行环境”这个概念帮助理解,但现代 JDK 的物理安装结构已经不同于 JDK 8 时代。
2.8 现代 Java 如何提供运行环境
现代 Java 提供:
jlink
等工具。
jlink 可以根据应用真正需要的模块创建:
自定义运行时镜像(Custom Runtime Image)
例如,一个程序只依赖部分 Java 模块,就可以构建专门服务于这个应用的运行环境。
概念上可以理解成:
完整 JDK
│
│ jlink
▼
应用需要的 Java 模块
│
▼
Custom Runtime Image
这比过去“所有用户都安装一个统一完整 JRE”的方式更加灵活。
初学阶段不要求掌握 jlink 命令。
这里只需要知道:
JDK 21 时代的 Java 运行环境已经进入模块化和可定制运行时阶段。
2.9 什么是 Java 核心类库
如果 JVM 只会执行字节码,却没有任何现成 API,那么 Java 开发仍然会非常困难。
所以 Java SE 还提供了大量标准 API。
例如以后会使用:
String
ArrayList
HashMap
LocalDate
BigDecimal
Thread
File
InputStream
Socket
这些 API 构成 Java 标准平台的重要部分。
在 Java 9 以后的模块化体系中,最基础的模块是:
java.base
其中包含大量最核心的 Java API。
例如:
java.lang
java.util
java.io
等基础能力与 Java SE 模块体系密切相关。
因此 Java 程序真正运行时并不只是:
程序 + JVM
还需要相关的:
Java 标准类库 / 模块
2.10 JDK、JRE、JVM、类库的职责对比
| 名称 | 全称 | 核心职责 | 初学理解 | | ------------- | ------------------------ | ------------------------- | --------------- | | JDK | Java Development Kit | 开发 + 运行 Java | 开发工具包 | | JRE | Java Runtime Environment | 提供 Java 运行环境的概念 | 运行环境 | | JVM | Java Virtual Machine | 加载和执行 class / 字节码 | Java 虚拟机 | | Java 标准类库 | Java SE API | 提供大量现成 Java 功能 | Java 自带工具箱 |
可以先记住一句话:
JDK 负责给开发者工具,JVM 负责执行 Java 字节码,标准类库负责提供程序可以直接使用的功能。
而 JRE 是描述 Java 程序运行所需整体环境的经典概念。
三、使用方法
3.1 Java 源代码
Java 程序通常首先存在于:
.java
源文件中。
例如:
public class HelloJava {
public static void main(String[] args) {
System.out.println("Hello Java");
}
}
文件:
HelloJava.java
这时它仍然属于:
Java 源代码(Source Code)
3.2 使用 javac 编译
经典编译方式:
javac HelloJava.java
编译成功以后通常产生:
HelloJava.class
因此:
HelloJava.java
│
│ javac
▼
HelloJava.class
3.3 .class 文件是什么
.class 不是普通文本源代码。
它使用:
Java Class File Format
保存编译后的程序信息。
其中最重要的是 JVM 能够执行的:
Bytecode
也就是:
字节码
所以不要把 .class 简单理解成:
“Windows 机器码文件”。
它是 Java 平台定义的二进制格式。
3.4 使用 java 启动程序
经典方式:
java HelloJava
注意:
这里写:
HelloJava
而不是:
HelloJava.class
java 启动器会启动 Java 运行过程,并让 JVM 加载需要执行的类。
完整理解:
HelloJava.java
│
│ javac
▼
HelloJava.class
│
│ java
▼
JVM
│
▼
程序执行
具体命令行操作和环境变量将在下一章专门练习。
四、原理与进阶
4.1 为什么不直接把 Java 编译成某种 CPU 的机器码
假设 Java 编译器直接产生 Windows x86 机器代码:
Java 源代码
│
▼
Windows x86 Machine Code
那么这个结果可能无法直接拿到:
Linux ARM
上运行。
因为:
- 操作系统不同;
- CPU 指令集不同;
- 系统调用不同;
- 二进制格式不同。
Java 的思路是在程序和真实机器之间增加:
JVM
于是变成:
┌── Windows JVM ── Windows
Java 代码 → class ├── Linux JVM ── Linux
└── macOS JVM ── macOS
Java 编译器主要面对统一的 JVM 体系,而不是直接为每一种操作系统重新生成一套源代码逻辑。
4.2 为什么 JVM 自己反而要区分平台
这是非常经典的一道问题:
Java 不是跨平台吗?为什么 Windows 和 Linux 还要下载不同 JDK?
因为跨平台的是:
Java 程序所面对的虚拟运行平台。
JVM 最终仍然需要和真实操作系统以及 CPU 打交道。
例如:
同一个 class
│
├── Windows JVM → Windows API → CPU
│
└── Linux JVM → Linux → CPU
所以:
Java 字节码尽量保持平台无关,而 JVM 的具体实现需要适配真实平台。
可以把 JVM 看成一个“翻译层”。
Java 程序只需要主要面向统一 JVM 规则,而不同 JVM 实现负责处理不同真实机器之间的差异。
4.3 “一次编译,到处运行”到底是什么意思
经典说法:
Write Once, Run Anywhere
也常被概括为:
一次编译,到处运行。
它真正表达的并不是:
“任何 Java 程序放到任何机器都百分之百直接运行。”
更严谨的含义是:
Java 源代码可以编译成平台相对独立的 class 文件,在具有兼容 Java 运行环境的平台上运行,而不需要针对每个平台重新编译一套本地机器代码。
核心链路:
Windows JVM
/
.class ─── Linux JVM
\
macOS JVM
真正承担“桥梁”作用的是:
JVM + 标准化 class 文件 / 字节码体系。
4.4 跨平台是有边界的
Java 跨平台能力很强,但不是魔法。
例如程序如果直接依赖:
- Windows 特有路径;
- Windows 特有命令;
- 原生 DLL;
- JNI 本地库;
- 某个平台独有硬件;
- 操作系统专有功能;
那么程序仍然可能出现平台依赖。
例如:
String path = "C:\\Users\\demo\\data.txt";
这种代码显然带有 Windows 路径特征。
所以 Java 的跨平台能力应该理解为:
Java 平台帮助开发者屏蔽大量平台差异,但程序本身仍需要避免或妥善处理平台相关依赖。
4.5 Java 字节码是不是机器码
不是。
机器码是某种真实 CPU 可以直接执行的指令。
例如不同 CPU 架构拥有自己的机器指令集。
Java 字节码则属于:
JVM 指令体系。
可以这样理解:
Java Bytecode
│
▼
JVM
│
▼
Native Machine Code
所以 class 文件中的字节码并不是直接交给 CPU 无条件执行。
4.6 JVM 怎么执行字节码
初学阶段可以理解成两种重要方式:
解释执行
JVM 可以读取字节码并执行相应操作。
JIT 编译
JIT:
Just-In-Time Compilation
即时编译。
现代 HotSpot JVM 会在程序运行过程中分析代码。
对于频繁执行的“热点代码”,可以把它编译为更适合当前真实机器执行的本地代码。
因此真实情况不是简单的:
Java = 纯解释执行
而更接近:
Bytecode
│
├── 解释执行
│
└── 热点代码 → JIT → 本地机器码
这样既保留了 Java 平台的抽象层,也能获得较好的运行性能。
4.7 .class 也存在版本兼容问题
跨平台不等于:
任意版本 JVM 都能运行任意版本 class。
class 文件本身具有:
Class File Version
例如 Java 21 对应自己的 class 文件版本。
如果使用较新的 JDK 编译代码,再拿给过老的 JVM 运行,可能出现:
UnsupportedClassVersionError
本质原因可以理解为:
老 JVM 不认识新版本 class 文件格式或新版本产生的字节码要求。
因此 Java 程序运行除了考虑:
操作系统兼容性
还要考虑:
Java 版本兼容性
五、实践应用
5.1 为什么服务端 Java 程序容易部署到 Linux
开发者可能在:
Windows
上编写 Java 程序。
服务器可能运行:
Linux
只要:
- Java 版本兼容;
- 所需依赖完整;
- 程序没有错误依赖 Windows 专有能力;
- Linux 上存在对应 Java 运行环境;
Java 程序通常不需要因为操作系统从 Windows 换成 Linux,就重新用另一种语言写一遍。
这正是 Java 平台化设计的重要价值。
5.2 为什么开发机器要安装 JDK
因为开发过程中不仅需要运行程序,还需要:
编译
调试
诊断
打包
生成文档
分析 JVM
所以开发环境直接安装:
JDK
即可。
5.3 为什么用户不一定必须安装完整 JDK
开发者需要完整开发工具。
最终用户只需要:
能把应用运行起来。
现代 Java 可以使用:
jlink
jpackage
等机制,把应用需要的运行环境与应用一起组织和分发。
因此:
开发环境
与:
最终运行环境
不一定完全相同。
六、常见问题
6.1 JDK、JRE、JVM 最简单怎么区分?
初学可以先记:
JDK:开发 Java
JRE:运行 Java 的环境概念
JVM:真正执行 Java 字节码的虚拟机
然后再补充现代 Java 的模块化运行时知识。
6.2 JDK 21 里面为什么找不到以前教程中的 jre 文件夹?
因为现代 JDK 的结构已经发生变化。
JDK 8 时代常见:
jdk/
└── jre/
现代 JDK 已经采用模块化运行时结构。
所以:
不要把“JDK 包含 JRE”永远理解成物理目录嵌套。
6.3 Java 真正跨平台的是 .java 文件吗?
源代码当然可以复制到不同平台。
但 Java 经典跨平台运行机制真正强调的是:
编译后的 class / 字节码面对统一 JVM 规范。
不同操作系统提供对应 JVM 实现。
6.4 JVM 为什么叫“虚拟机”?
因为它定义了一套抽象计算机模型:
- 指令;
- 数据类型;
- class 文件;
- 运行时数据区域;
- 执行规则。
Java 程序可以主要面向这套虚拟机器规则,而不是直接面向每种真实 CPU。
6.5 JVM 和 HotSpot 是一回事吗?
不完全是。
JVM 是:
Java Virtual Machine 的规范和抽象概念。
HotSpot 是:
非常重要的一种 JVM 具体实现。
6.6 .class 文件可以直接双击运行吗?
不要把 .class 当成普通操作系统原生可执行文件理解。
它需要 Java 运行环境和 JVM 按 Java class 文件规则加载执行。
6.7 .class 是不是完全平台无关?
class 文件格式本身被设计为硬件和操作系统无关的二进制格式。
但是最终应用是否能够无修改运行,还可能受到:
- Java 版本;
- 第三方依赖;
- 原生库;
- 文件系统;
- 操作系统功能;
等因素影响。
6.8 JDK 和 Java SE 是一回事吗?
不是完全相同的概念。
Java SE 是:
标准 Java 平台及其规范/API。
JDK 是:
用于开发和运行 Java 程序的一套具体开发工具。
例如 Java SE API 中以:
java.*
为主的是标准平台 API。
而 JDK 本身还包含很多:
jdk.*
相关工具和 JDK 专有能力。
七、练习与验收
7.1 知识问答
- 什么是 JDK?为什么开发 Java 程序通常直接安装 JDK?
- 什么是 JVM?为什么 JVM 被称为“虚拟机”?
- 什么是 JRE?在 JDK 21 环境中应该怎样理解 JRE?
- JDK、JRE、JVM、Java 标准类库分别解决什么问题?
.java与.class文件有什么区别?- 什么是 Java 字节码?
javac与java分别主要负责什么?- Java 真正实现跨平台的核心机制是什么?
- 为什么 Windows、Linux、macOS 需要不同的 JVM/JDK 构建?
- JVM 与 HotSpot 有什么区别?
- 为什么“Java 一次编译,到处运行”不是无条件成立的?
- 为什么较新的 class 文件可能无法在较老 JVM 上运行?
7.2 代码阅读
阅读下面的 Java 程序,不运行:
public class HelloJVM {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a + b);
}
}
回答:
- 当前看到的内容属于 Java 源代码还是 Java 字节码?
- 假设文件名为
HelloJVM.java,经过javac后通常会产生什么文件? - CPU 是否直接执行这段 Java 源代码?
- JVM 在整个执行链路中位于什么位置?
- 如果把生成的 class 文件从 Windows 复制到 Linux,在什么条件下具备运行可能?
7.3 手写代码
在不查看现成代码的情况下:
- 手写一个
HelloJVM类。 - 定义标准
main方法。 - 输出一句:
Hello JVM
然后在纸上写出完整流程:
源代码
→ 编译器
→ class 文件
→ JVM
→ 操作系统 / CPU
本题重点不是代码难度,而是建立完整运行模型。
7.4 Debug
下面是一位初学者写出的解释:
“javac 会把 Java 源代码直接编译成 Windows 机器码,所以在 Windows 编译出来的程序只能运行在 Windows。如果想运行到 Linux,就必须重新编译。”
请找出其中的核心概念错误,并重新画出:
Java Source
Class File
JVM
Operating System
CPU
之间的正确关系。
7.5 综合训练
假设存在下面三个环境:
电脑 A
Windows 11
JDK 21
服务器 B
Linux
JDK 21
服务器 C
Linux
JDK 17
电脑 A 使用 JDK 21 默认配置编译出一个 class 文件。
请分析:
- 为什么服务器 B 通常具备直接运行它的基础条件?
- 为什么服务器 C 可能出现 Java 版本兼容问题?
- 这里讨论的“跨平台”和“跨 Java 版本”是不是同一个问题?
- 如果程序调用了 Windows 独有的 DLL,即使服务器 B 有 JDK 21,还能否简单声称一定跨平台?
- 为了让程序真正具备较好的可移植性,开发者还应该避免哪些平台依赖?
不要只回答“能”或“不能”,必须说明理由。
7.6 本章验收
在不查看教程的情况下完成:
- [ ] 画出 JDK、JRE、JVM、Java 类库之间的职责关系图。
- [ ] 口述
.java → .class → JVM → CPU的完整链路。 - [ ] 解释
javac与java的职责区别。 - [ ] 解释为什么 JVM 自己需要区分不同操作系统。
- [ ] 解释“JDK 21 中为什么不能机械寻找一个 jre 文件夹”。
- [ ] 解释 JVM 与 HotSpot 的关系。
- [ ] 解释 Java 跨平台能力的三个边界:Java 版本、原生依赖、操作系统依赖。
- [ ] 能够向一个没有学过 Java 的人解释“一次编译,到处运行”真正是什么意思。