首页 / Java 入门教程 / JDK、JRE 与 JVM 的关系

Java 入门教程

JDK、JRE 与 JVM 的关系

本教程共 100 篇 · 第 3 篇 · 更新于 2026-08-05 · 约 5 分钟阅读

JavaJava 入门教程JDKJREJVM字节码

3. JDK、JRE 与 JVM 的关系

本节目标:分清 JDK、JRE、JVM 三个缩写各自负责什么,理解字节码怎么被执行,装环境时知道该装哪个。

三个缩写,一层套一层

初学者最容易被这三个词绕晕。先记全称:

  • JVM = Java Virtual Machine,Java 虚拟机
  • JRE = Java Runtime Environment,Java 运行时环境
  • JDK = Java Development Kit,Java 开发工具包

它们不是三个并列的东西,而是一层套一层的包含关系:

 ┌─   ┌────────────────────────────────────┐
 │    │  javac、jar、javadoc、jshell ...   │  ← 开发工具
 │    └────────────────────────────────────┘
JDK ┌─┌────────────────────────────────────┐
 │  │ │                                    │
 │ JRE│      JVM  +  标准类库              │  ← 运行所需
 │  │ │                                    │
 └─ └─└────────────────────────────────────┘
      ┌───────┐┌───────┐┌───────┐┌───────┐
      │Windows││ Linux ││ macOS ││others │  ← 操作系统
      └───────┘└───────┘└───────┘└───────┘
```bash

一句话版本:**JDK 包含 JRE,JRE 包含 JVM**。

要写 Java,装 JDK 就够了,它把另外两个都带上了。

## JVM:真正干活的那一层

JVM 是这套体系的核心。它是一个**软件模拟出来的「计算机」**,有自己的指令集、自己的内存布局。

它干四件事:

1. **加载**:把 `.class` 文件读进内存。
2. **校验**:检查字节码是否合法,防止恶意代码搞破坏。
3. **执行**:把字节码指令翻译成当前 CPU 能懂的机器码并运行。
4. **管内存**:分配对象、回收垃圾(GC),不用你手动释放。

如 §01 所述,JVM 像翻译官——字节码是「世界语」,JVM 负责讲成当地话。

关键在于:**Windows、Linux、macOS 各有各的 JVM,但它们读的是同一份字节码**。跨平台就是这么实现的。

> [!NOTE]
> 严格说 JVM 是一份「规范」,不是一个具体软件。Oracle HotSpot、OpenJ9、GraalVM 都是它的实现。日常说 JVM 一般指 HotSpot。

## 字节码长什么样

光说不练没感觉。写个最短的程序看看:

```java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello");
    }
}

编译它,然后用 JDK 自带的 javap 反汇编:

javac Hello.java
javap -c Hello.class
```java

你会看到类似这样的输出:

```text
Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
       0: aload_0
       1: invokespecial #1     // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7     // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #13    // String Hello
       5: invokevirtual #15    // Method java/io/PrintStream.println:(...)V
       8: return
}

getstaticldcinvokevirtual 就是字节码指令,长得像汇编但不属于任何真实 CPU。

它们只有 JVM 认识。这就是「字节码是 JVM 的机器语言」这句话的含义。

Tip

#7#13 这些编号指向常量池,不同 JDK 版本编出来的数字会不一样,不用对着抄。看懂指令名就够了。

解释执行 + JIT:Java 为什么不慢

早期 Java 被吐槽慢,因为 JVM 一条条解释字节码,比机器码慢一大截。

现在的 JVM 加了 JIT(Just-In-Time)编译器。它会盯着程序运行,发现某段代码被反复执行(比如循环里的方法),就把它直接编译成本地机器码缓存起来。

下次再跑到这段代码,走的是机器码,速度接近 C。

所以现代 Java 的性能表现是:启动稍慢,跑起来很快。长期运行的服务端程序正好吃这个红利。

JRE:只想跑,不想写

JRE = JVM + 标准类库。

标准类库就是 StringArrayListMath 这些你即将天天用到的现成类。它们已经编译成字节码,打包在 JDK 里。

只装 JRE 的机器可以运行 Java 程序,但没有编译器,写不了代码。

Warning

从 Java 11 开始,Oracle 不再单独发布 JRE 安装包。现在统一装 JDK;如果只想给用户一个精简运行时,用 jlink 工具自己裁剪一个。老教程里「先装 JRE 再装 JDK」的说法已经过时了。

JDK:开发者要的那一套

JDK 在 JRE 之上补齐了开发工具。装完 JDK,在它的 bin 目录下能找到一堆可执行文件:

命令作用
java启动 JVM,运行程序
javac编译器,.java.class
jar把一堆 .class 打包成 .jar
javadoc从文档注释生成 HTML API 文档
javap反汇编,查看字节码
jshell交互式命令行(Java 9 引入),随手试语法
jdb命令行调试器
jlink裁剪定制运行时镜像

初学阶段用得最多的是 javacjavajshell 也值得记住,试一小段语法不用建文件。

class 文件版本号:高版本编的跑不了低版本

每个 .class 文件里都记着一个版本号,标明它是哪个 JDK 编出来的。

Java 版本class 文件版本
Java 852
Java 1155
Java 1761
Java 2165
Java 2569

规则很简单:高版本 JVM 能跑低版本字节码,反过来不行

用 Java 25 LTS 编译的类,扔到 Java 17 的环境里运行,会直接抛:

java.lang.UnsupportedClassVersionError: Hello has been compiled by a more
recent version of the Java Runtime (class file version 69.0), this version
of the Java Runtime only recognizes class file versions up to 61.0
```bash

这个错误初学者踩得非常多,通常是本机 JDK 和服务器 JDK 版本不一致造成的。看到 `UnsupportedClassVersionError`,第一反应就该是查版本。

## 一个常被忽略的事实:JVM 不只跑 Java

JVM 认的是字节码,它并不关心这份字节码是从哪种语言编译来的。

于是出现了一批「JVM 语言」:

| 语言 | 特点 | 常见场景 |
|------|------|---------|
| Kotlin | 语法简洁,空安全 | Android、后端 |
| Scala | 函数式特性强 | 大数据(Spark) |
| Groovy | 动态类型,脚本友好 | Gradle 构建脚本 |
| Clojure | Lisp 方言 | 特定领域 |

它们编译出的 `.class` 文件和 Java 编译出的没有本质区别,能互相调用。

这也解释了为什么第 2 章说「学 Java 转 Kotlin 成本很低」——底层跑的是同一套东西。

反过来看,把 JVM 吃透的收益不止于一门语言。类加载、内存模型、GC 这些知识,换到 Kotlin、Scala 上照样成立。

## 完整流程串一遍

把上面的内容连起来,一个 Java 程序的一生是这样:

```text
你写代码          Hello.java        (纯文本)
   │ javac 编译

class 文件        Hello.class       (版本号 69)
   │ java 启动 JVM

JVM 加载 → 校验 → 解释执行 / JIT 编译 → 输出结果

javac 属于 JDK,java 启动的是 JVM,运行时用到的 String 等类来自 JRE 的标准类库。

三者各司其职,缺一环程序就跑不起来。

小结

JDK 套着 JRE,JRE 套着 JVM,开发只需要装 JDK。

JVM 负责加载、校验、执行字节码和管理内存,是跨平台的实现者;JIT 让长期运行的程序性能接近本地代码。

JRE 是 JVM 加标准类库,Java 11 起不再单独发布。

class 文件带版本号,高版本 JVM 兼容低版本字节码,反向会报 UnsupportedClassVersionError