跨平台的 Java 与跨语言的 JVM
「一次编写,到处运行」的代价
1995 年,Sun 公司提出了 Java 的核心理念:Write Once, Run Anywhere(一次编写,到处运行)。这句话在当时是革命性的。
在 Java 之前,C/C++ 程序员写程序,需要针对不同平台编译不同的机器码。Windows 上的 .exe 和 Linux 上的可执行文件是不同的。程序员要在多个平台上运行程序,工作量翻倍。
Java 的解决方案是:引入一个中间层——字节码。源代码不再直接编译成机器码,而是编译成一种虚拟 CPU 能理解的指令集。这个虚拟 CPU,就是 JVM。
C/C++ 的做法:
源代码 → [编译] → Windows 机器码
源代码 → [编译] → Linux 机器码
源代码 → [编译] → macOS 机器码
每个平台都要单独编译
Java 的做法:
源代码 → [编译] → 字节码 (.class)
字节码 → [JVM 解释/编译] → 任意平台的机器码
一次编译,到处运行「代价」在于:字节码需要被 JVM 解释或编译执行,多了一层中间转换,极端情况下比原生代码稍慢。但 JIT 编译器的出现大幅弥补了这个差距。
JVM:超越 Java 的存在
JVM 的设计者可能没想到,这个最初为 Java 设计虚拟机,后来成为了多语言运行的平台。
如今,跑在 JVM 上的语言远不止 Java 一门:
| 语言 | 诞生年份 | 定位 |
|---|---|---|
| Java | 1995 | 通用企业级开发 |
| Groovy | 2003 | 脚本语言,动态特性 |
| Scala | 2004 | 函数式+面向对象,大数据 |
| Kotlin | 2011 | 现代 Android/服务端开发 |
| Clojure | 2007 | Lisp 方言,函数式 |
| JRuby | 2001 | Ruby 实现,跑 Ruby on Rails |
| Ceylon | 2011 | Red Hat 推出的企业语言 |
| Kotlin | 2011 | JetBrains 出品,Google 官方 Android 语言 |
有一个笑话:JVM = Java Virtual Machine,后来变成了 JVM = Many Very Magic。
为什么这么多语言选择 JVM?
原因很实际:
- 成熟的运行时:内存管理、GC、线程调度、JIT 优化——这些脏活累活 JVM 帮你干了,语言实现者只需要专注于语法和语义
- 现成的生态:Java 积累了几十年的库,语言实现者可以直接拿来用
- 跨平台能力:编译成字节码,就天然支持所有有 JVM 的平台
- 高性能:JIT 编译器可以根据运行时信息做激进优化,比纯解释执行快很多
跨平台的关键:字节码规范
让多语言能跑在同一个 JVM 上,核心是遵守同一个规范:Class 文件格式。
《Java 虚拟机规范》定义了 Class 文件的结构——魔数、常量池、字段表、方法表、属性表……无论什么语言,只要你的编译器能生成符合这个规范的 Class 文件,就能被 JVM 加载和执行。
这就是 JVM 的厉害之处:它不关心源代码是什么语言写的,它只关心字节码对不对。
Java 编译器 → Java 字节码
Kotlin 编译器 → Java 字节码
Scala 编译器 → Java 字节码
Groovy 编译器 → Java 字节码
↓
统一的 Class 文件格式
↓
JVM 统一执行跨平台与 JDK/JRE/JVM 的关系
很多人分不清 JDK、JRE、JVM 的区别:
| 组件 | 全称 | 包含内容 | 用途 |
|---|---|---|---|
| JVM | Java Virtual Machine | 只包含运行时组件 | 运行已编译的字节码 |
| JRE | Java Runtime Environment | JVM + Java 核心类库 | 运行 Java 程序 |
| JDK | Java Development Kit | JRE + javac + 调试工具 | 开发 Java 程序 |
简单记:
- 开发用 JDK(你需要编译)
- 运行用 JRE(用户只需要跑程序)
- JVM 是底座(JRE 和 JDK 里都有它)
平台特定与平台无关
在 Java 中,有两个维度需要注意:
平台无关的:
- Java 源代码(
.java文件)—— 用任何文本编辑器都能写 - 编译后的字节码(
.class文件)—— 所有平台的 JVM 都能读
平台相关的:
- JVM 实现——每个平台有各自的实现(Windows 版 HotSpot、Linux 版 HotSpot……)
- 本地方法(native 方法)—— 通过 JNI 调用平台特定的 C/C++ 代码
- 直接内存—— 使用操作系统原生内存,不受 JVM 堆大小限制
理解这个区分,有助于理解为什么 Java 能跨平台,同时也有一些「平台陷阱」需要注意。
AOT 编译:另一种思路
JVM 传统的执行模式是解释+JIT 编译,即字节码在运行时被逐步翻译成本地机器码。
但从 JDK 9 开始,引入了一种新的编译模式:AOT(Ahead-Of-Time)编译。
AOT 的做法是:在程序运行之前,直接把字节码编译成本地机器码。这样运行时就不需要 JIT 编译器了,启动速度更快,适合对启动时延敏感的容器化场景。
不过 AOT 也有局限性:它无法像 JIT 那样根据运行时数据进行激进优化,所以性能通常不如 JIT。因此 JIT 仍然是主流,AOT 作为补充手段。
本节小结
跨平台的 Java,核心靠的是 JVM 这个中间层:
- Java 源代码 →
javac→ 字节码(平台无关) - 字节码 + JVM → 本地机器码(各平台执行)
JVM 的野心不止于 Java。它定义了通用的字节码规范,让任何语言只要能编译出合规的 Class 文件,就能跑在 JVM 上。这造就了今天 JVM 上多语言共存的生态。
下一节,我们来深入聊聊 字节码与多语言混合编程,看看字节码长什么样,以及为什么 Kotlin 和 Java 可以无缝互调。
