Java 版运行时与发布环境
从 JVM、JIT、GC 到 Launcher、Mod 生态与版本迁移,理解 Java 这套运行环境为 Minecraft 带来了什么能力、付出了什么代价,以及为什么这个选择既塑造了 Mod 生态的繁荣,也带来了性能与发布的约束。
2009 年,Notch 选择 Java 编写 Minecraft。这个决定带来了跨平台能力、快速迭代和意外繁荣的 Mod 生态,同时也带来了 GC 停顿、启动延迟和必须捆绑运行时的发布约束。
十几年后,Java Edition 仍然运行在 JVM 上,但这套运行环境本身也在演化:从 Java 8 到 17、21,从 LWJGL 2 到 3,从 G1 GC 到 ZGC,从手动安装 Java 到 Launcher 自动下载匹配的运行时。同时,社区通过 Mod(Sodium、Lithium、OptiFine)证明了在 JVM 内仍有巨大的优化空间,而 Bedrock Edition 用 C++ 重写则证明了换语言确实能获得不同的性能曲线——但也付出了 Mod 生态几乎消失的代价。
本章要回答的核心问题是:
Java / JVM 这套运行环境为 Minecraft 带来了什么能力,又限制了什么?这个选择如何塑造了今天的 Java Edition,包括它的性能特征、Mod 生态和发布模型?
我们会看到,Java 不是 Minecraft 的"本质",而是一个历史选择。这个选择的后果既包括跨平台、可修改字节码和低门槛 Modding,也包括 GC 停顿、JIT 预热和启动延迟。理解这些权衡,才能理解为什么"Minecraft 卡是因为 Java"这句话既有道理又不准确,也能理解为什么"立刻换掉 Java"不是免费的。
本文主要以 Java Edition 1.21.4 / Java 21 为当前参照。历史节点会在相关小节标注。
1为什么选择 Java?(2009)
在讨论 JVM 的技术特性之前,先回到 2009 年的语境:为什么 Notch 选择 Java?
1.1独立开发者的现实约束
2009 年,Notch 是一名独立开发者,白天在 King.com 工作,晚上和周末开发 Minecraft。1 他需要:
快速原型
- Java 有自动内存管理(不需要手动
malloc/free); - 有丰富的标准库(集合、I/O、网络);
- 有成熟的 IDE(Eclipse、NetBeans);
- 编译快、迭代快。
跨平台发布
- "Write once, run anywhere":同一份
.jar文件可以在 Windows、macOS、Linux 上运行; - 不需要为每个操作系统维护独立的构建流程。
现成的游戏库
- LWJGL(Lightweight Java Game Library)提供了 OpenGL、OpenAL 和输入管理的 Java 绑定;2
- 可以直接用 OpenGL 渲染 3D 图形,不需要从头写渲染引擎。
Web 发布(早期)
- Java Applet 可以嵌入网页,Minecraft Classic 最初就是在浏览器里运行的;3
- 这让分发变得非常简单:玩家只需要打开网页。
1.2对比:如果选择 C/C++
如果 Notch 在 2009 年选择 C/C++,他会面对:
- 需要为 Windows、macOS、Linux 各编译一份二进制;
- 需要管理内存(手动分配、释放,调试内存泄漏);
- 跨平台 OpenGL 绑定需要自己处理(或使用 SDL、GLFW);
- 发布需要打包不同操作系统的安装包。
对一个人的原型项目来说,这些成本太高了。
1.3Java 的选择是"2009 年的正确"
Java 让 Notch 能够:
- 快速迭代游戏逻辑;
- 一次编译,跨平台运行;
- 使用成熟的游戏库(LWJGL);
- 通过 Applet 或
.jar轻松分发。
这不是"Java 是唯一选择",而是"在当时的约束下,Java 是最合理的选择"。
Infiniminer(Minecraft 的灵感来源)也是用 C# / XNA 编写的,同样是托管语言。卷一第 01 章《从 Infiniminer 到 Cave Game》讨论了这段历史。
2JVM 与字节码:可移植性的代价
Java 程序不直接编译成机器码,而是编译成字节码(Bytecode),运行在 Java 虚拟机(JVM) 上。
2.1字节码与跨平台
Source Code (.java)
↓ javac 编译
Bytecode (.class)
↓ JVM 加载
Machine Code (运行时)优势:
- 同一份
.class或.jar文件可以在任何安装了 JVM 的平台上运行; - 开发者不需要为每个平台编译独立的二进制。
代价:
- 需要安装 JVM(早期玩家常遇到"You need to install Java");
- 启动时需要加载 JVM、类库和游戏代码;
- 运行时有额外的抽象层(JVM → 机器码)。
2.2Class Loading 与热加载
JVM 通过 ClassLoader 动态加载类:
ClassLoader loader = new URLClassLoader(new URL[]{ modJar });
Class<?> modClass = loader.loadClass("com.example.MyMod");这个机制为 Mod 生态提供了基础:
- Mod Loader(Forge、Fabric)可以在运行时加载额外的
.jar文件; - 可以通过反射(Reflection)访问和修改游戏类的字段和方法;
- 可以通过字节码转换(Mixin)在加载时修改游戏代码。
这是 Java Mod 生态繁荣的关键:
字节码是可读的、可修改的,而且可以在运行时加载。这让社区能够在没有官方 Mod API 的情况下扩展游戏。
第 14 章《Mod、Mixin 与加载器》详细讨论了这套机制。
2.3对比:C++ 的 Modding
如果 Minecraft 用 C++ 编写,Modding 会变成:
- 需要访问源码或逆向工程二进制;
- Mod 需要编译成与游戏二进制兼容的动态库(
.dll/.so); - 二进制层面的 Hook 需要更底层的技术(DLL 注入、内存补丁);
- 跨平台 Mod 需要为每个平台编译。
Bedrock Edition 就是这个现实:
- 用 C++ 编写,性能更好;
- 但 Modding 生态远不如 Java Edition 繁荣;
- 官方提供 Add-On / Script API,但能力受限。
第 17 章《Bedrock:另一套实现》会详细对比两者。
3JIT 与性能:为什么"刚进游戏会卡"
JVM 不是传统的解释器,也不是提前编译(AOT)。它使用 即时编译(JIT)。
3.1分层编译
HotSpot JVM 使用分层编译:4
- 解释执行:代码第一次运行时,逐条解释字节码(慢);
- C1 编译器:快速编译热点代码,生成优化程度较低的机器码(快速启动);
- C2 编译器:对频繁执行的代码进行深度优化(高性能)。
3.2为什么刚进游戏会卡
玩家常常抱怨"刚进世界很卡,玩一会儿就好了"。原因:
- 游戏刚启动时,大部分代码还在解释执行或 C1 编译;
- 区块生成、实体 AI、红石更新等热路径需要被执行多次后,C2 才会介入深度优化;
- 加载 Mod 时,Mixin 转换、类加载、反射初始化都需要时间。
这不是 Bug,而是 JIT 的工作方式:
JIT 用"前几分钟慢一点"换取"之后快很多"。
3.3JIT 优化的局限
即使 C2 深度优化后,某些模式仍然难以优化:
- 频繁的对象分配:每 Tick 创建大量临时对象(如
BlockPos、Vec3); - 多态调用:接口方法调用(如
Block.onUse)难以内联; - 反射调用:Mod 大量使用反射访问私有字段。
这些问题不是"JVM 慢",而是"Java 代码写成这样"。
优化 Mod(如 Lithium)通过改算法和减少分配来提升性能,证明了问题不完全在语言层。5
4GC 与停顿:为什么服务器会"卡一下"
Java 使用自动垃圾回收(GC),开发者不需要手动释放内存。
4.1GC 的工作方式
New Object → Young Gen → Old Gen
↓ ↓
Minor GC Major GC/Full GC
(快, 频繁) (慢, 罕见)- Minor GC:清理年轻代(Young Generation),通常几毫秒;
- Major/Full GC:清理老年代(Old Generation)或整个堆,可能几十到几百毫秒。
4.2为什么 GC 会导致卡顿
第 04 章《游戏循环、Tick 与调度》讨论了服务器的 50ms Tick Budget:
服务器需要在 50 毫秒内完成一个 Tick(区块更新、实体 AI、红石、玩家输入等),才能维持 20 TPS。
如果 GC 在 Tick 中触发:
- Minor GC:5-10ms → 勉强可以接受;
- Full GC:50-200ms → 整个 Tick 超时,TPS 下降。
玩家和运营者会看到:
[Server thread/WARN] Can't keep up! Is the server overloaded?
Running 1500ms or 30 ticks behind4.3不同的 GC 算法
| GC 算法 | 特点 | 适用场景 |
|---|---|---|
| Serial GC | 单线程,STW 时间长 | 小堆,单核 |
| Parallel GC | 多线程,吞吐量高,STW 仍较长 | 离线任务 |
| G1 GC | 分区回收,暂停可控 | Java 9+ 默认,适合 Minecraft |
| ZGC | 超低延迟(10ms以下),需要大内存 | Java 15+,大型服务器实验 |
| Shenandoah | 并发回收,低延迟 | Java 12+,实验性 |
现代 Minecraft 服务器推荐 G1 GC,它将堆分成小块(Region),逐块回收,避免一次性停顿过长。
4.4GC 不是"Java 的错"
GC 停顿的根本原因是:
主循环在 50ms 内分配了太多临时对象。
改进方向:
- 减少分配(对象池、复用);
- 调整 GC 参数(堆大小、GC 算法);
- 优化算法(减少不必要的计算)。
Paper、Lithium 等优化通过改算法减少分配,而不是"换掉 Java"。6
5Launcher 与版本隔离
早期玩家需要手动下载 minecraft.jar,手动安装 Java,手动管理 Mod。
2012 年,Mojang 推出官方 Minecraft Launcher,后来演化成今天的版本管理系统。
5.1Launcher 的职责
版本隔离
- 每个 Minecraft 版本(1.18.2、1.19.4、1.20.1 等)有独立的:
- 游戏 JAR 文件;
- 资源文件(assets);
- 库依赖(libraries);
- Mod 文件夹(如果使用 Mod Loader)。
Java 版本管理
- 1.17 要求 Java 16;
- 1.18 要求 Java 17;
- 1.20.5+ 要求 Java 21。7
Launcher 会自动下载匹配的 捆绑 JRE(Java Runtime Environment),玩家不再需要手动安装 Java。
认证与账户
- Mojang 账户 / Microsoft 账户;
- 在线验证(防盗版)。
Mod Loader 支持
- 第三方 Launcher(MultiMC、Prism Launcher、HMCL)可以:
- 安装 Forge / Fabric / NeoForge;
- 管理 Mod 列表;
- 隔离不同的 Modpack 配置。
5.2版本隔离的价值
为什么需要版本隔离?
- Mod 兼容性:不同版本的 Mod 不能混用;
- 存档兼容性:某些旧世界在新版本可能出问题;
- 测试与开发:开发者需要在不同版本间切换。
代价:
- 磁盘空间:每个版本独立存储(几百 MB 到几 GB);
- 启动时间:每次启动都要加载 JVM、类库、Mod。
5.3第三方 Launcher
官方 Launcher 功能有限,社区开发了更强大的替代品:
- MultiMC / Prism Launcher:实例隔离、Mod 管理、自定义 Java 参数;
- HMCL(Hello Minecraft! Launcher):中文社区流行,支持多账户、下载加速;
- ATLauncher:Modpack 自动安装。
这些 Launcher 本质上是:
管理 JVM 启动参数、类路径(Classpath)和游戏目录的工具。
它们让玩家可以在不同的 Minecraft 版本、不同的 Mod 配置间无缝切换。
6Java 版本迁移:从 Java 8 到 21
Minecraft 的 Java 版本要求随时间演进:
| Minecraft 版本 | 最低 Java 版本 | 发布时间 |
|---|---|---|
| 1.12.2 及以前 | Java 8 | 2017 以前 |
| 1.17 | Java 16 | 2021.06 |
| 1.18+ | Java 17 | 2021.11 |
| 1.20.5+ | Java 21 | 2024 |
6.1为什么要升级 Java 版本?
新语言特性
- Java 17:Sealed Classes、Pattern Matching、Records;
- Java 21:Virtual Threads、Sequenced Collections。
性能改进
- 更好的 GC(ZGC、Shenandoah);
- 更快的 JIT 优化;
- 更低的内存占用。
安全修复
- 旧版本 Java 不再接收安全更新。
6.2迁移的代价
Mod 兼容性破坏
- Mod 需要重新编译(
.class文件格式变化); - Mixin 需要适配新的字节码版本;
- 反射 API 可能有变化(如 Java 16 的模块系统限制)。
玩家端的混乱
- "我装了 Java 8,为什么 1.18 不能玩?"
- Launcher 自动下载 JRE 解决了这个问题,但第三方 Launcher 需要手动配置。
示例:Java 16 的模块系统
Java 9+ 引入模块系统,限制了对 JDK 内部 API 的反射访问。某些 Mod 依赖这些 API,需要添加 JVM 参数:
--add-opens java.base/java.lang=ALL-UNNAMED这类"魔法参数"成为 Mod 社区的常见问题。
7Java 与 Mod 生态的共生关系
回到最初的问题:为什么 Java Edition 的 Mod 生态如此繁荣?
7.1字节码是可修改的
与编译成机器码的 C/C++ 程序不同,Java 字节码:
- 可以被反编译成接近源码的形式;
- 可以被工具(如 Mixin)在加载时修改;
- 可以通过反射在运行时访问私有字段和方法。
这降低了 Modding 的门槛:
- 不需要逆向工程二进制;
- 不需要内存补丁或 DLL 注入;
- 可以用 Java 本身编写 Mod(而不是汇编或 C)。
7.2没有官方 API,但有事实 API
Mojang 长期没有提供官方 Mod API,但社区通过:
- 反编译:MCP(Mod Coder Pack)将混淆的字节码还原成可读代码;
- Mapping:Yarn、Mojmap 等项目提供人类可读的类名和方法名;
- Mixin:在加载时注入代码,不需要修改原始 JAR。
这些工具都依赖 Java 字节码的可读性和可修改性。
7.3对比:Bedrock 的 Modding 困境
Bedrock Edition 用 C++ 编写,Modding 生态远不如 Java Edition:
- 二进制难以反编译;
- 官方提供 Add-On / Script API,但能力受限;
- 第三方 Mod 需要逆向工程和二进制补丁,门槛极高。
这证明了:
Java 的可修改字节码不是 Minecraft 的"本质",但它是 Mod 生态繁荣的关键enabler。
第 14 章《Mod、Mixin 与加载器》详细讨论了这套机制。
8性能优化:社区 vs 官方
Java Edition 的性能优化历史,几乎是一部"社区先行、官方跟进"的历史。
8.1客户端优化 Mod
OptiFine(~2011)
- 最早的性能优化 Mod;
- 优化渲染(动态光照、更好的剔除、Shader 支持);
- 减少 Chunk 重建次数。
Sodium(2020)
- 现代渲染引擎重写;
- 大幅提升 FPS(2-4 倍);
- 与 Fabric 生态兼容。8
Lithium(2020)
- 优化游戏逻辑(AI、物理、Tick);
- 减少不必要的计算和分配。
Iris(2021)
- Shader 支持(兼容 OptiFine Shader);
- 与 Sodium 配合使用。
8.2服务端优化
Paper(2016–)
- Spigot 的高性能分支;
- 优化 Chunk Loading、Entity Tick、Redstone;
- 配置选项更细粒度。9
Folia(2023–)
- Paper 的实验性分支;
- 区域化多线程(Regionised Threading);
- 突破单线程 Tick 限制,但 Plugin 需要重写。10
第 15 章《插件、服务端软件与规模化》详细讨论了服务端优化。
8.3为什么社区能优化,官方不直接采用?
兼容性约束
- 官方需要保证所有资源包、数据包、旧存档仍能工作;
- 社区 Mod 可以"破坏兼容性,换取性能"。
范围约束
- OptiFine / Sodium 只需要优化渲染;
- Mojang 需要同时维护 Java / Bedrock、PC / 主机 / 移动。
测试成本
- 社区 Mod 由用户自愿测试;
- 官方更新需要覆盖数百万玩家、数千种硬件配置。
历史行为
- Paper 的某些优化会改变 Redstone 时序、刷怪机制;
- 官方不能轻易改变这些"历史 Bug 变特性"的行为。
这不是"官方懒",而是:
官方背负的兼容性约束远大于社区 Mod。
第 18 章《兼容、重构与技术债》会详细讨论这个权衡。
9原生库与 LWJGL
Java 客户端从第一天起就不是"纯 Java"。
9.1LWJGL:Java 的 OpenGL 绑定
LWJGL(Lightweight Java Game Library)提供:2
- OpenGL 绑定(3D 渲染);
- OpenAL 绑定(音频);
- GLFW 绑定(窗口管理、输入);
- STB 库(图像加载、字体渲染)。
这些都是原生库(Native Libraries),通过 JNI(Java Native Interface)从 Java 调用。
9.2LWJGL 2 → LWJGL 3 迁移(1.13)
Minecraft 1.13 进行了一次大规模的 LWJGL 升级:11
LWJGL 2(旧)
- 基于旧的 OpenGL 固定管线;
- 窗口管理用 Display / Mouse / Keyboard。
LWJGL 3(新)
- 现代 OpenGL 核心配置(Core Profile);
- 窗口管理用 GLFW;
- 更好的多显示器支持。
后果:
- 所有依赖 LWJGL 2 API 的 Mod 失效;
- Mod 开发者需要学习新的 GLFW API;
- 旧教程失效。
这是一次"技术债清理",但也是一次"生态震荡"。
9.3为什么不是"纯 Java"
Java 不适合直接操作图形硬件,因为:
- OpenGL / Vulkan 是 C API;
- GPU 驱动是原生代码;
- 性能关键路径(顶点缓冲、纹理上传)需要直接操作内存。
因此,Minecraft 的架构是:
游戏逻辑(Java)
↓
LWJGL(JNI)
↓
OpenGL / GLFW / OpenAL(C)
↓
GPU 驱动(原生)这意味着:
渲染性能不完全取决于 JVM,也取决于 OpenGL 调用的效率和 GPU 驱动。
10换语言的代价:Bedrock 的证明
Bedrock Edition 用 C++ 重写,已经证明了换语言的可能性和代价。
获得的:
- 更好的性能(尤其是移动设备);
- 更低的内存占用;
- 更快的启动速度;
- 跨平台能力(主机、移动、VR)。
付出的:
- Mod 生态几乎消失(C++ 二进制难以修改);
- Add-On / Script API 能力远不如 Java Mod;
- 某些玩法语义分叉(Redstone、刷怪机制);
- 社区工具链薄弱。
如果今天要把 Java Edition "换成 C++",成本不在语法(把 class 改成 struct),而在于:
必须重新实现的:
- 所有的红石机器(Quasi-Connectivity、更新顺序);
- 所有的刷怪机(生成规则、Despawn 距离);
- 所有的存档格式(NBT、Chunk Format);
- 所有的 Mod 接口(Mixin、反射、事件系统需要用别的方式实现)。
Bedrock 的现实是:换语言确实能改变性能曲线,但会导致语义分叉。两套实现今天仍在追求 Parity(功能对等),但某些差异已经难以消除——不是因为技术不可能,而是因为两边的玩家社区已经依赖各自的历史行为。
第 17 章《Bedrock:另一套实现》会详细对比两者的架构与权衡。
11这一章留下什么
从 JVM、JIT、GC 到 Launcher、Mod 生态与版本迁移,Java 这套运行环境既是 Minecraft 能力的来源,也是约束的来源。
2009 年,Java 让 Notch 能够快速原型、跨平台发布、使用 LWJGL 渲染 3D 世界。这个选择带来了:跨平台能力(一份 JAR 对三个操作系统)、低门槛 Modding(字节码可反编译、可修改、可在运行时加载)、版本隔离(Launcher 管理不同版本和 Java 运行时)。但同时也带来:GC 停顿(Full GC 可能打断服务器 Tick)、JIT 预热(刚进游戏时性能较差)、启动延迟(加载 JVM、扫描 Mod、应用 Mixin)、发布约束(需要捆绑 JRE,每次大版本更新需要新的 Java 版本)。
社区通过 Mod(Sodium、Lithium、Paper)证明了在 JVM 内仍有巨大的优化空间,而 Bedrock 用 C++ 重写则证明了换语言确实能获得不同的性能曲线——但也付出了 Mod 生态几乎消失的代价。Java 版本从 8 迁移到 17、21 的过程,也说明了运行环境本身在演化:新的 GC 算法(ZGC)、新的语言特性(Records、Virtual Threads)都在改善性能和开发体验,但每次迁移也会破坏 Mod 兼容性。
对爱好者来说,"Minecraft 卡是因为 Java"这句话既有道理又不准确。GC 停顿、JIT 预热确实是 JVM 带来的约束,但很多"卡"来自算法(Entity AI、区块转换、红石更新)和硬件(GPU、内存、磁盘 I/O),而不是语言本身。社区优化 Mod(Sodium、Paper)证明了在 Java 内仍能大幅提升性能。Launcher 自动下载 JRE 解决了"需要手动安装 Java"的问题,但版本隔离也带来了磁盘空间和启动时间的代价。
对开发者来说,能抄走的是:字节码 + 动态加载 = 低门槛 Modding(但需要工具链支持:反编译、Mapping、Mixin);版本隔离 + Launcher 让玩家可以在多个版本和配置间切换;JIT 的分层编译用"启动慢一点"换取"运行快很多";原生库绑定(JNI)让托管语言也能调用 GPU。不能照抄的是:Java 的具体字节码格式、HotSpot JVM 的优化策略、LWJGL 的 API 设计。真正值得继承的是:可移植 + 可扩展的发布模型;字节码可修改性是 Mod 生态的关键(但不是唯一方式);GC 停顿不是"语言的错",而是"分配策略的后果";换语言能改变性能曲线,但会破坏生态和语义。
注释
-
Notch 在 2009 年白天在 King.com 工作,晚上和周末开发 Minecraft。见卷一第 01 章《从 Infiniminer 到 Cave Game》。 ↩
-
LWJGL(Lightweight Java Game Library),https://www.lwjgl.org/。提供 OpenGL、OpenAL、GLFW 等的 Java 绑定。 ↩ ↩2
-
Minecraft Classic 最初是 Java Applet,嵌入网页运行。见 https://classic.minecraft.net/。 ↩
-
Oracle,《HotSpot JVM Performance Tuning》。分层编译(Tiered Compilation)在 Java 7+ 默认启用。 ↩
-
CaffeineMC,《Lithium》,https://github.com/CaffeineMC/lithium-fabric。优化游戏逻辑和 AI。 ↩
-
PaperMC,https://papermc.io/。Paper 通过修改 Chunk Loading、Entity Tick 等优化性能。 ↩
-
Minecraft Wiki,《Java Edition version history》。1.17 要求 Java 16,1.18 要求 Java 17,1.20.5+ 要求 Java 21。 ↩
-
CaffeineMC,《Sodium》,https://github.com/CaffeineMC/sodium-fabric。现代化的渲染引擎重写。 ↩
-
PaperMC,https://papermc.io/。Spigot 的高性能分支,优化服务端性能。 ↩
-
PaperMC,《Folia》,https://github.com/PaperMC/Folia。实验性区域化多线程服务端。 ↩
-
Minecraft Wiki,《Java Edition 1.13》。LWJGL 从 2 升级到 3,窗口管理从 Display 迁移到 GLFW。 ↩