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 知识问答

  1. 什么是 JDK?为什么开发 Java 程序通常直接安装 JDK?
  2. 什么是 JVM?为什么 JVM 被称为“虚拟机”?
  3. 什么是 JRE?在 JDK 21 环境中应该怎样理解 JRE?
  4. JDK、JRE、JVM、Java 标准类库分别解决什么问题?
  5. .java.class 文件有什么区别?
  6. 什么是 Java 字节码?
  7. javacjava 分别主要负责什么?
  8. Java 真正实现跨平台的核心机制是什么?
  9. 为什么 Windows、Linux、macOS 需要不同的 JVM/JDK 构建?
  10. JVM 与 HotSpot 有什么区别?
  11. 为什么“Java 一次编译,到处运行”不是无条件成立的?
  12. 为什么较新的 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);
    }
}

回答:

  1. 当前看到的内容属于 Java 源代码还是 Java 字节码?
  2. 假设文件名为 HelloJVM.java,经过 javac 后通常会产生什么文件?
  3. CPU 是否直接执行这段 Java 源代码?
  4. JVM 在整个执行链路中位于什么位置?
  5. 如果把生成的 class 文件从 Windows 复制到 Linux,在什么条件下具备运行可能?

7.3 手写代码

在不查看现成代码的情况下:

  1. 手写一个 HelloJVM 类。
  2. 定义标准 main 方法。
  3. 输出一句:
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 文件。

请分析:

  1. 为什么服务器 B 通常具备直接运行它的基础条件?
  2. 为什么服务器 C 可能出现 Java 版本兼容问题?
  3. 这里讨论的“跨平台”和“跨 Java 版本”是不是同一个问题?
  4. 如果程序调用了 Windows 独有的 DLL,即使服务器 B 有 JDK 21,还能否简单声称一定跨平台?
  5. 为了让程序真正具备较好的可移植性,开发者还应该避免哪些平台依赖?

不要只回答“能”或“不能”,必须说明理由。


7.6 本章验收

在不查看教程的情况下完成:

  • [ ] 画出 JDK、JRE、JVM、Java 类库之间的职责关系图。
  • [ ] 口述 .java → .class → JVM → CPU 的完整链路。
  • [ ] 解释 javacjava 的职责区别。
  • [ ] 解释为什么 JVM 自己需要区分不同操作系统。
  • [ ] 解释“JDK 21 中为什么不能机械寻找一个 jre 文件夹”。
  • [ ] 解释 JVM 与 HotSpot 的关系。
  • [ ] 解释 Java 跨平台能力的三个边界:Java 版本、原生依赖、操作系统依赖。
  • [ ] 能够向一个没有学过 Java 的人解释“一次编译,到处运行”真正是什么意思。