Minecraft 设计思想
Volume III · ImplementationExtensibility and the Two Engines

The Java Edition Runtime

What choosing Java in 2009 bought (portability, decompilability) and what it cost (GC pauses, memory, startup); the repayment history of the runtime tax; LWJGL and the native border; the language was an accident — and the ecosystem the accident grew has become essence.

This chapter's thesis: Java was the right choice in 2009's context — it delivered portability (write once, run on every platform) and reversibility (bytecode that can be recovered: the technical foundation of the mod ecosystem) in the same package, at the price of a runtime tax (GC pauses, memory overhead, slow startup), and the better part of that tax has since been repaid by fifteen years of engineering evolution. The language choice was a historical accident, but the ecosystem it grew has been woven into essence; any one-sided verdict — "the language must be replaced" or "the language must never be replaced" — is wrong.

1The problem

"Minecraft lags because of Java." This is the most widely circulated technical judgment in the player community, and a textbook case of half right. Right: Java does bring real runtime overhead, and early Minecraft really was constrained by it. Wrong: today's Minecraft runs nowhere near slow on a modern JVM — and had it picked any other language back then, it most likely would not have lived long enough to be optimized at all.

A language choice is the closest thing software engineering has to a birth: it does not decide what you become, but it decides what you most easily grow into. This chapter settles the whole account of that birth: in 2009's option pool, where Java was right; what the price was; how the tax got repaid year by year; where the engine's border with the native world runs — and finally it answers the question Volume IV cannot avoid asking: is the language essence, or accident? (Preview: the answer is two-sided.)

2The right choice in 2009

Judging a technical choice requires returning to the option pool first. In 2009, a Swedish programmer coding after hours wants to write a cross-platform three-dimensional game. The realistic options number about three. C++ — the king of performance, but cross-platform means handling builds and system differences separately for Windows, macOS, and Linux; one person cannot pay that engineering bill. Flash — unmatched reach in the browser, but its 3D ability is feeble; it cannot carry a voxel world. Java — write once, run anywhere; ready-made OpenGL bindings for three dimensions (LWJGL, a lightweight library that wraps OpenGL, OpenAL, and the other native libraries for Java's use1); plus one signature trick, covered below.

In this pool, Java is nearly the exclusive answer. And portability, for early Minecraft, was not icing on the cake — it was the distribution strategy itself: a player's friends might be on any operating system, and the officials could not afford a storefront slot on any single platform; Java is what made "ship one jar file, and any computer can run it" true. The word-of-mouth growth engine of Volume I, chapter 2 burned precisely this portability. Browser play — the Java applet technology of the day, running Java programs directly inside a web page — made "click a link and play" the smoothest entry point of 2009; the technology died later, but it served through the growth years.

And that signature trick — the bytecode can be reversed — had its ecosystem significance fully accounted in Volume III, chapter 12: Java programs ship as half-finished intermediate code, tools can restore it into source close enough to readable, and so the community could read the game's body and modify it. The last technical precondition, from ModLoader and Forge to the entire mod era, was this language itself. Portability supplied the distribution; reversibility seeded the second authorship's ecosystem — of Java's gifts to Minecraft, not one is written in the performance column.

3The runtime tax: GC, the hot path, and startup

The price was just as real, and it comes in three main items.

GC pauses. Java's memory management is delegated to the garbage collector: the program just allocates, and the collector periodically sweeps up objects no longer in use. The cost is that some phases of collection must "pause the world" — the term of art is stop-the-world, and it means what it says: while the sweeping lasts, every application thread stands still. Pauses run from a few to a few dozen milliseconds, which players feel as the occasional small stutter. Minecraft's world updates allocate large numbers of short-lived objects every single tick (the chain updates and entity settlement of chapters 4 and 5 are both allocation heavyweights), landing exactly on the GC's busiest workload. The good news: short-lived objects are precisely the breed collectors handle best — objects born fast and dying young can be reclaimed extremely quickly. The bad news: a fresh batch arrives every tick, and the bill never stops.

Memory overhead. The JVM and Java's object model carry fixed costs: every object drags an object header along, and if every world block were modeled as an object, each one would be pure overhead (the real implementation stopped storing blocks that way long ago — palettes and bit packing, chapter 1 — but entities, interfaces, and strings remain an ocean of objects). At equal visuals, Java Edition's memory footprint generally runs above that of an equivalent native implementation.

Startup time. A cold JVM start must load and verify enormous numbers of class files, and the JIT — the just-in-time compiler — needs the code to run first before it can compile the hot paths into machine code. "The game takes forever to open, and then it stutters a while longer once you're in": half of that is class loading, half is JIT warmup.

The three taxes share one shape: they are not constants but living accounts that fluctuate with scale and activity — the bigger the world, the more entities, the more elaborate the contraptions, the heavier the tax. This is also why "entities are the tax" (chapter 6) so often gets conflated with "the Java tax": a great deal of "Java lag" is really entity tax, with the invoices simply mailed to the language.

4The performance record: how one tax was repaid

Fifteen years on, how much of these three taxes has actually been repaid? The books look far better than the community's impression.

The JVM's own progress is the largest line item: a new generation of garbage collectors has pressed pauses down to the millisecond scale and spread them more evenly, and each JIT generation optimizes more aggressively than the last — the same code runs far faster today than it did in 2010, and the shelf life of "Java is slow" expired long ago. The officials' optimizations advanced release by release: the light engine rewritten (chapter 4), chunk meshing improved (The Client and Rendering), continuous tuning of entities and scheduling — performance never ranked first among the officials' priorities, but the account kept being paid down all the same. The community's performance mods took the contract for the remainder (chapter 9's "unofficial official implementations"): the Sodium family in the rendering layer, assorted optimization mods in the logic layer, Paper's finely tuned GC parameters on the server side. The most important fact they prove: the remaining performance gap lives in the rendering and algorithm layers, not the language layer. On the same machine, with the implementation swapped out, Java flies; the community has already finished the controlled experiments that locate which layer the bottleneck belongs to.

So the only genuine tax left today is the structural cost of the object model and the sheer existence of the GC — they hold Java Edition's performance ceiling below that of a theoretical native implementation. This tax is real, but it lives at the level of "enthusiast benchmarks," not at the level of "player experience": for the overwhelming majority of players, today's Java Edition bottleneck is rendering algorithms, not the language.

One winner in the performance history deserves a separate entry, and it wins on no benchmark: the official launcher and version isolation. Java's distribution form — one jar file (Volume I, chapter 2) — made "any number of versions coexisting on one computer" technically almost free: the launcher keeps each version's files in its own separate directory (version isolation), and a player can move freely between today's newest release and an old one from 2012 without the two polluting each other. Volume I, chapter 13 covered the cultural weight of this (old saves have an official door back); here is its technical root: Java's distribution form plus the launcher's directory management together form the architectural foundation of this game's "history can be revisited." Bedrock Edition cannot do this — the app stores ship only the latest version — and Bedrock as a Second Implementation completes the contrast. Incidentally, the launcher is itself a cleanup site of Java's technical debt: it rewrote itself from a bloated old program into a lightweight modern application — when even the gatekeeper is repaying debt, you can glimpse this household's debt culture.

The server side keeps a repayment history worth recording too: a considerable share of host tuning on large servers (the Paper family — chapter 16 expands) is JVM tuning — choosing the better-fitting garbage collector, adjusting heap sizes, optimizing parameter combinations. The same server code, before and after tuning, performs like two different machines. This craft of "giving the JVM a checkup" is one of the core skills of server operations, and it confirms from another angle: Java's performance floor is low, but the door to the ceiling is open, and the key is in the operators' hands.

5Native libraries and the render backend: the border of the Java world

Java is not an island. LWJGL is its customs house: OpenGL (rendering), OpenAL (audio), GLFW (windows and input) — native libraries of the C world, connected into the game through JNI, Java's official bridge to native code. The history of the render backend is the trade history of this customs house: every generational change, from legacy OpenGL usage to the modern core pipeline (chapter 9's brief history of rendering), was renegotiated at the JNI border — a native call pays a fixed cost each way, and border design (how much work to leave on the native side, how much to keep on the Java side) is performance-sensitive fine work.

This border has one easily misread spot: the bill for "Java games lag" usually has native-library and driver-layer charges mixed into it. Driver compatibility trouble, OpenGL's scheduling, GPU vendors' implementation differences — hardships any language must face; but because they happen to a "Java game," they get charged to Java as well. Part of the language's bad name is debt it carries for the whole stack.

6Accident versus essence: a two-sided verdict

The chapter's acceptance verdict can now be delivered, and by the book's discipline it is two-sided.

What Java proved: a language choice can be ecosystem strategy. Portability let an independent game distribute itself across the entire network; reversibility seated the players' modifications as the product's second authorship. Neither of these is "the language's performance"; both are "the language's sociology." Without Java, 2009's Minecraft might have performed better — but it likely would neither have spread (the cross-platform costs) nor been changed (no decompilation): it would have remained more of a "game," and never grown into a "medium."

What Java held back: a structural discount on the performance ceiling; the experience tax of startup and memory; and one hidden item — for years the officials could skip setting any performance KPI at all ("it's Java, what do you expect" is too convenient a shield). Debt can be outsourced (chapter 9); that does not make it owed with a clear conscience.

Is the language accident or essence? Choosing Java was an accident — the craft Notch happened to know best. But what the accident grew — the decompilable ecosystem, LWJGL's border, the entire toolchain — has become part of what "Minecraft is." The true cost of changing languages is not rewriting those hundreds of thousands of lines of engine code (Volume IV will argue that part is feasible); it is rewriting the whole society that grew around the language: the mod toolchain, the de-obfuscation mappings, the performance mods, the server ecosystem. The accident was long ago woven into the essence — which is precisely the first case Volume IV, chapter 3, Accidents That Can Be Discarded, sets out to dissect.

7What this chapter leaves behind

  • For players: the next time you hear "it's Java, so it lags," you can supply the other half: it's Java, so it can host mods, so it crossed every platform, so it survived fifteen years — and the lagging part, the community and the officials together, has spent fifteen years repaying. It is nearly paid off.
  • For developers: what you can take away is the ecological evaluation of a language choice — beyond the benchmark table, evaluate "what does this language let players do to you" (portable means distribution; reversible means ecosystem), plus the honesty of a tax repayment schedule: write the runtime's costs as an installments plan that can be paid down, not as a one-time veto. What you cannot take away is the timing: Java being the optimal answer in 2009 does not make it the answer today — a technology choice stays valid only until the day its ecosystem freezes.

注释

  1. Wikipedia, "Lightweight Java Game Library" (LWJGL: an open-source library providing Java bindings for OpenGL, OpenAL, and other native libraries, the dependency beneath Minecraft), Wikipedia, retrieved 2026-09-18: see https://en.wikipedia.org/wiki/LWJGL . ↩

On this page