本教程基于 Oracle 官方《The Java Virtual Machine Specification》(Java SE 26)第 1 章《Introduction》整理编写。它是一份「读规范前先读」的导读------帮你搞清楚 JVM 是什么、为什么存在、规范这本书到底在讲什么、以及怎么正确地读这本「天书」。
你将学到
- Java 和 JVM 是怎么诞生的,为什么会有「一次编写,到处运行」
- 一个反直觉的关键事实:JVM 根本不认识 Java 这门语言
class文件版本号(major.minor)是什么,以及 Java SE 26 对应的版本号- 整本 JVM 规范的章节地图,以及哪些内容「搬家」去了《Java 语言规范》
- 规范的「记号约定(Notation)」------怎么看懂它那些字体和
§x.y引用 - 什么是「预览特性(Preview Features)」,以及它为什么被锁死在某个版本
前置知识
- 写过 Java,知道
.java文件要编译成.class才能跑 - 听过「JVM 是 Java 的虚拟机」这种说法(本篇会纠正它的不严谨之处)
- 不需要任何 JVM 内部知识,这一章本来就是给「零基础读规范」的人准备的
一、历史:JVM 是为了「网上的安全小程序」而生的
很多人以为 Java 一开始就是给后端/企业级用的,其实不是。
Java 是一门通用、并发、面向对象 的语言,语法像 C/C++,但砍掉了那些让 C/C++ 复杂、易错、不安全的特性。它最初是为了联网的消费类设备(networked consumer devices) 而设计的------目标是在不同硬件架构上安全地分发软件组件。
真正让它爆红的是 万维网(WWW):
- 浏览器让普通人能轻松看多媒体内容,但 HTML 太「静态」、表达力有限
HotJava浏览器第一次展示了「把程序嵌进 HTML 页面」的能力:程序跟着 HTML 一起下载,下载前会被严格检查是否安全,然后才运行- 这些编译后的程序和 HTML 一样,与网络、主机无关------不管从哪来、跑在什么机器上,行为一致
于是有了那句著名的口号:Write Once, Run Anywhere(一次编写,到处运行)。
一句话:JVM 的初心是「让一段二进制代码能安全地跨网络、跨机器运行」。这个初心,决定了它后面所有的设计。
二、JVM 是什么:一台「抽象的计算机器」
规范里对 JVM 的定位非常精准:
The Java Virtual Machine is the cornerstone of the Java platform.(Java 平台的基石)
它负责三件事:
- 硬件与操作系统无关性(你编译一次,到处能跑)
- 编译后代码体积小
- 保护用户免受恶意程序侵害
再看更本质的一句:
The Java Virtual Machine is an abstract computing machine.(JVM 是一台抽象的计算机器)
它和真实机器一样,有指令集 ,并在运行时操作各种内存区域 。用虚拟机来实现一门语言并不稀奇------最有名的先驱是 UCSD Pascal 的 P-Code 机器。
2.1 关键事实:JVM 不认识 Java
这是整章最重要的一句话,请记住:
The Java Virtual Machine knows nothing of the Java programming language, only of a particular binary format, the
classfile format.
JVM 只认一种二进制格式------class 文件。一个 class 文件里包含:
- JVM 指令(也就是字节码 bytecode)
- 一个符号表(symbol table)
- 其他辅助信息
这带来一个威力巨大的推论:
任何能用合法
class文件表达功能的语言,都能跑在 JVM 上。
所以今天你能在 JVM 上跑 Kotlin、Scala、Groovy、Clojure......不是因为它们「像 Java」,而是因为它们都编译成了 class 文件。JVM 是它们的「交付载体(delivery vehicle)」。
纠正一个常见误解:「JVM 是 Java 的虚拟机」这话不严谨。更准确的说法是:JVM 是
class文件的执行引擎,Java 只是它的第一个、也是最著名的宿主语言。
2.2 JVM 不绑定任何实现技术
另一点容易被忽略:JVM 不假定任何特定的实现技术、主机硬件或操作系统。
- 第一个原型是 Sun 公司在一台手持 PDA 类设备上用软件模拟出来的
- Oracle 现在的实现能在手机、桌面、服务器上运行
- 它本质上不是解释器 ------既可以被解释,也可以把指令集直接编译成硅片 CPU 的指令(这就是 JIT / AOT)
- 甚至可以用微码(microcode)或直接用硬件电路实现
这就是为什么同一个
.class在不同 JVM 上「行为一样、性能不同」------行为由规范管,性能由实现管。
三、class 文件版本号:它锁定了你能跑在多新的 JVM 上
class 文件是带版本号 的,格式为 major.minor(主版本.次版本),表示这个文件依赖哪个 Java SE 发布版,并影响 JVM 如何解读它。
本版规范(Java SE 26)的 JVM:
- 兼容《Java 语言规范 Java SE 26 版》
- 支持
class文件 major 版本号 45 ~ 70(含) - 工具通常会采用最新的 major 版本号来用上新特性;但旧版本号的文件一般也能在未来的 JVM 上继续跑
下面这张表是官方给出的「Java SE 发布版 ↔ class 文件主版本号」对照(节选自规范 Table 1.2-A):
| Java SE 发布 | 发布时间 | 支持的最早主版本 | 支持的最新主版本 |
|---|---|---|---|
| 1.0.2 | 1996-05 | 45 | 45 |
| 1.1 | 1997-02 | 45 | 45 |
| 1.2 | 1998-12 | 45 | 46 |
| 1.4 | 2002-02 | 45 | 48 |
| 5.0 | 2004-09 | 45 | 49 |
| 6 | 2006-12 | 45 | 50 |
| 7 | 2011-07 | 45 | 51 |
| 8 | 2014-03 | 45 | 52 |
| 11 | 2018-09 | 45 | 55 |
| 17 | 2021-09 | 45 | 61 |
| 21 | 2023-09 | 45 | 65 |
| 22 | 2024-03 | 45 | 66 |
| 23 | 2024-09 | 45 | 67 |
| 24 | 2025-03 | 45 | 68 |
| 25 | 2025-09 | 45 | 69 |
| 26 | 2026-03 | 45 | 70 |
记一个实用数字:Java SE 26 → class 文件 major 70 。你用
javap -v看到的major version: 70就对应 JDK 26 编译出来的类。
四、规范的章节地图:这本书到底讲什么
第 1 章还把整本书的骨架画了出来,读之前先有个地图:
| 章节 | 内容 |
|---|---|
| 第 2 章 | JVM 架构总览(数据类型、运行时数据区、栈帧、指令集...) |
| 第 3 章 | 把 Java 语言代码编译成 JVM 指令集 |
| 第 4 章 | class 文件格式(与平台无关的二进制格式) |
| 第 5 章 | JVM 的启动,以及类/接口的加载、链接、初始化 |
| 第 6 章 | 指令集,按操作码助记符的字母顺序排列 |
| 第 7 章 | 操作码助记符按操作码数值索引的对照表 |
4.1 哪些内容「搬走了」
有个重要变化值得注意:早期版本(第二版)里,规范自己附带了「Java 语言概览」和「线程与锁的内存模型」两章;在 Java SE 26 版里,这两块被移到了《Java 语言规范》(JLS):
- Java 语言本身 → 看 The Java Language Specification, Java SE 26 Edition
- 线程与锁 → 看 JLS 第 17 章(源自 JSR 133 专家组制定的《Java 内存模型与线程规范》)
所以现在的 JVM 规范更「纯粹」:它只讲「
class文件 + 指令集 + 运行时语义」,语言层面的事交给 JLS。读的时候别找错书。
五、怎么读这本规范:记号约定(Notation)
规范有大量固定的「排版暗语」,提前看懂能省很多事:
- 单个标识符
N→ 默认指java.lang包下的类/接口(如写String就是java.lang.String);其它包必须用全限定名。 java.*及其子包下的类 → 默认指「由**引导类加载器(bootstrap class loader)**加载」的那个。- 交叉引用 :规范内部用
(§x.y);引用 JLS 时用(JLS §x.y)。 - 字体含义 :
等宽字体:JVM 数据类型、异常、错误、class文件结构、Prolog 代码、Java 代码片段- 斜体:JVM「汇编语言」(操作码 opcode 和操作数)、运行时数据区的条目、新术语、强调
- 非规范性文本(non-normative) :用小号、缩进的文字呈现,用来给「直觉、理由、建议、例子」做补充说明------它不是强制规定,但能帮你理解。
读规范时养成习惯:看到小号缩进文字,知道那是「解释」,不是「规定」;看到
§4.1这种,直接跳过去对照。
六、预览特性(Preview Features):为什么一个新语法会被「锁死」在某个版本
「预览特性」是 Java 近年迭代的重要机制,第 1 章专门讲它:
一个预览特性可以是:
- 一门语言的新特性(preview language feature)
- JVM 的一个新特性(preview VM feature)
java.*/javax.*命名空间里新增的模块/包/类/接口/字段/方法等(preview API)
它的特点是:完整定义、完整实现,但「临时」 。发布出来是为了收集真实使用的开发者反馈,可能将来变成永久特性,也可能被改掉或移除。
6.1 限制:默认关闭,且不能只开一部分
- 编译期和运行期都默认禁用预览特性,必须由用户显式开启
- 实现不允许「只开启其中某几个」预览特性------要么全开,要么全关
6.2 一个容易踩坑的细节:预览 class 文件被绑死在版本上
一个 class 文件「依赖 Java SE N(N≥12)的预览特性」,当且仅当:
- 它的 major 版本号 = Java SE
N支持的最新主版本号 - 它的 minor 版本号 = 65535
举例:版本号 66.65535 依赖 Java SE 22 的预览特性(因为 66 是 Java SE 22 支持的最新主版本)。
关键后果 :这类 class 文件被绑死在那个发布版上 ,即使你开启了预览特性,也不能在别的发布版 JVM 上加载------因为不同发布版的预览特性内容可能不同甚至缺失。
这解释了为什么你有时用 JDK 22 开了
--enable-preview编译出的类,拿到 JDK 23 上跑会直接失败。它不是 bug,是规范刻意设计的隔离。
6.3 当前状态
规范明确写到:Java SE 26 没有定义任何预览 VM 特性(preview language feature / API 另算,由对应文档说明)。