Minecraft 设计思想
卷三 · 技术实现平台、兼容与长期演化

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 能够:

  1. 快速迭代游戏逻辑;
  2. 一次编译,跨平台运行;
  3. 使用成熟的游戏库(LWJGL);
  4. 通过 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

  1. 解释执行:代码第一次运行时,逐条解释字节码(慢);
  2. C1 编译器:快速编译热点代码,生成优化程度较低的机器码(快速启动);
  3. C2 编译器:对频繁执行的代码进行深度优化(高性能)。
字节码 解释执行(慢) 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 behind

4.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版本隔离的价值

为什么需要版本隔离?

  1. Mod 兼容性:不同版本的 Mod 不能混用;
  2. 存档兼容性:某些旧世界在新版本可能出问题;
  3. 测试与开发:开发者需要在不同版本间切换。

代价:

  • 磁盘空间:每个版本独立存储(几百 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 82017 以前
1.17Java 162021.06
1.18+Java 172021.11
1.20.5+Java 212024

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 停顿不是"语言的错",而是"分配策略的后果";换语言能改变性能曲线,但会破坏生态和语义。

注释

  1. Notch 在 2009 年白天在 King.com 工作,晚上和周末开发 Minecraft。见卷一第 01 章《从 Infiniminer 到 Cave Game》。 ↩

  2. LWJGL(Lightweight Java Game Library),https://www.lwjgl.org/。提供 OpenGL、OpenAL、GLFW 等的 Java 绑定。 ↩ ↩2

  3. Minecraft Classic 最初是 Java Applet,嵌入网页运行。见 https://classic.minecraft.net/。 ↩

  4. Oracle,《HotSpot JVM Performance Tuning》。分层编译(Tiered Compilation)在 Java 7+ 默认启用。 ↩

  5. CaffeineMC,《Lithium》,https://github.com/CaffeineMC/lithium-fabric。优化游戏逻辑和 AI。 ↩

  6. PaperMC,https://papermc.io/。Paper 通过修改 Chunk Loading、Entity Tick 等优化性能。 ↩

  7. Minecraft Wiki,《Java Edition version history》。1.17 要求 Java 16,1.18 要求 Java 17,1.20.5+ 要求 Java 21。 ↩

  8. CaffeineMC,《Sodium》,https://github.com/CaffeineMC/sodium-fabric。现代化的渲染引擎重写。 ↩

  9. PaperMC,https://papermc.io/。Spigot 的高性能分支,优化服务端性能。 ↩

  10. PaperMC,《Folia》,https://github.com/PaperMC/Folia。实验性区域化多线程服务端。 ↩

  11. Minecraft Wiki,《Java Edition 1.13》。LWJGL 从 2 升级到 3,窗口管理从 Display 迁移到 GLFW。 ↩

On this page