大白话带你认识 JVM
JVM 是 Java 跨平台能力的根基:源码先编译成平台无关的字节码,再由各平台上的虚拟机实现负责执行。这篇文章讲清三件事——JDK、JRE、JVM 的关系,HotSpot 的即时编译机制,以及「一次编译,到处运行」的真实含义。
JDK、JRE、JVM 是什么关系
三者是逐层包含的关系:
- JVM(Java Virtual Machine):运行字节码的虚拟机,负责类加载、内存管理、垃圾回收和指令执行。它本质上是一套规范,HotSpot 是应用最广的实现,此外还有 OpenJ9、GraalVM 等。
- JRE(Java Runtime Environment):JVM 加上运行所需的 Java 核心类库(如
java.lang、java.util),只够把程序”跑起来”。 - JDK(Java Development Kit):JRE 再加上开发工具链——编译器
javac、诊断工具jstack、jmap等,面向开发者。
日常说”装 Java”,装的都是 JDK。JDK 9 引入模块化之后,还可以用 jlink 裁剪出只包含所需模块的精简运行时,传统意义上独立分发的 JRE 已经逐步淡出。
「一次编译,到处运行」的真实含义
这句话更准确的说法是:一次编译成字节码,字节码可以交给任何平台上的 JVM 执行。真正跨平台的不是 Java 程序本身,而是字节码这个中间层——每个操作系统都有各自的 JVM 实现,由它把统一格式的字节码翻译成当前平台的机器指令,平台差异被 JVM 这一层吸收掉了。
代价体现在三个方面:
- 性能上的间接成本:字节码不是机器码,不能直接交给 CPU,要么逐条解释执行,要么在运行时动态编译成本地代码。这带来启动慢、需要预热才能到达峰值性能的问题,也是 JIT 存在的根本原因。
- 行为差异并未消失:不同平台、不同 JVM 实现、甚至同一 JVM 的不同版本,在默认参数、线程调度、文件编码等细节上并不完全一致,跨平台部署仍然需要在目标环境实测。
- 跨平台不是无条件的:一旦通过 JNI 调用本地库,或依赖特定操作系统的特性,代码就被绑死在对应平台上。
更务实的理解是:JVM 抹平了大部分平台差异,但没有消除验证和适配的工作。
HotSpot 与即时编译
HotSpot 的名字来自它的核心思路:与其把所有代码都编译一遍,不如只把”热点”代码编译好。
解释执行与编译执行
程序启动时,字节码先由解释器逐条执行,优点是启动快。执行过程中,JVM 统计方法的调用次数和循环回边次数,超过阈值的”热点代码”被提交给即时编译器(JIT,Just-In-Time Compiler)编译成本地机器码并缓存,后续调用直接走本地代码。这种”边运行边编译”的模式,让 Java 程序往往越跑越快。
C1 与 C2 编译器
HotSpot 内置两个编译器,定位不同:
- C1(客户端编译器):编译速度快、优化少,追求尽快用上本地代码。
- C2(服务端编译器):编译耗时,但会做方法内联、逃逸分析、锁消除、循环展开等激进优化,产出高质量机器码,是峰值性能的来源。
分层编译
早期需要在 C1(-client)和 C2(-server)之间二选一。JDK 7 引入、JDK 8 起默认开启分层编译(Tiered Compilation),两者协作,代码按热度逐级升迁:
- 第 0 层:解释执行,收集 profiling 信息;
- 第 1~3 层:C1 编译,分别对应不带 profiling、带少量 profiling、带完整 profiling 三种程度;
- 第 4 层:C2 编译,利用前期收集的运行数据做激进优化。
值得注意的一点是:C2 的激进优化建立在运行时统计的假设上,假设一旦被推翻(比如内联的方法出现了新的子类实现),JVM 会执行”去优化”,退回解释执行重新收集数据。JIT 编译是动态的、可回退的,而不是一锤子买卖。
小结
- JDK 包含 JRE,JRE 包含 JVM;JVM 是规范,HotSpot 是最常用的实现。
- 跨平台的是字节码,代价是解释与运行时编译的间接成本,以及平台差异带来的验证成本。
- HotSpot 用解释器保证启动速度,用 C1、C2 分层编译换取峰值性能,热点探测与去优化机制让它始终基于真实运行数据做决策。

