Learn · concepts

Was ist JVM-Bytecode? Java-Klassendateien erklärt

Der Bytecode der JVM ist der kompakte Befehlssatz, den die Java Virtual Machine ausführt. Erfahren Sie, wie .class-Dateien funktionieren, warum Mods Bytecode umschreiben und wo ASM ins Spiel kommt.

TRtrolPublished 5 min read

Was ist JVM-Bytecode?

JVM-Bytecode ist der Befehlssatz, den die Java-Virtuelle Maschine ausführt. Wenn Sie Java-Quellcode kompilieren, erzeugt der Compiler keinen Maschinencode für Ihre CPU. Stattdessen erzeugt er Bytecode, ein kompaktes, portables Format, das in .class-Dateien gespeichert wird. Die JVM liest diese Anweisungen zur Laufzeit und wandelt sie in nativen Code um.

Dieser gestaffelte Ansatz – von der Quelle zum Bytecode und dann zum Maschinencode – ist es, was Java portabel macht. Die gleiche .class-Datei läuft auf Windows, macOS, Linux oder jedem anderen System mit einer kompatiblen JVM, weil die JVM die Schicht ist, die weiß, wie die Anweisungen auf der lokalen Maschine ausgeführt werden müssen. Schreiben Sie einmal, führen Sie überall aus.

Wie Quellcode zu Bytecode wird

Eine .java-Datei ist Text, den ein Mensch schreibt. Eine .class-Datei ist die kompilierte Ausgabe, die die JVM ausführt. Der Compiler liest Ihren Quellcode, überprüft ihn und schreibt eine .class-Datei pro Klasse, die Bytecode-Anweisungen sowie einen Constant Pool, Methodentabellen und andere Metadaten enthält.

Bytecode ist nicht an einen bestimmten Prozessor gebunden. Die gleiche .class-Datei läuft auf Windows, macOS, Linux oder jedem System mit einer Java-Laufzeitumgebung, weil die JVM die Schicht ist, die weiß, wie die Anweisungen auf der lokalen Maschine ausgeführt werden müssen. Genau das ist der Sinn und Zweck dieses Formats.

Wie die Anweisungen aussehen

Bytecode ist ein stapelbasierter Befehlssatz. Die meisten Operationen legen Werte auf einen Operandenstapel, entfernen sie wieder und legen ein Ergebnis ab. Es gibt keine Registerzuweisung, die es zu berücksichtigen gilt; das Modell ist absichtlich einfach, damit die JVM es schnell verifizieren und ausführen kann.

Einige Beispiel-Opcodes geben einen Eindruck:

OpcodeWas er tut
iloadLegt eine lokale Integer-Variable auf den Stapel
iaddEntfernt zwei Integers vom Stapel, legt ihre Summe ab
invokevirtualRuft eine Instanzmethode auf
getfieldLiest ein Instanzfeld
returnKehrt von einer Methode zurück

Man liest oder schreibt diese selten manuell. Tools generieren und verarbeiten sie, und hier kommt eine Bibliothek wie ASM ins Spiel.

Warum Bytecode für Mods wichtig ist

Bytecode ist wichtig, weil er es ermöglicht, das Verhalten zu ändern, ohne den ursprünglichen Quellcode zu haben. Ein Minecraft-Mod hat fast nie den Quellcode des Spiels. Was er hat, ist das kompilierte Spiel auf der Festplatte. Um eine Funktion hinzuzufügen oder zu ändern, liest ein Mod den vorhandenen Bytecode, schreibt ihn um und speist das Ergebnis zurück an die JVM, wenn die Klassen geladen werden.

So fügen Patching-Frameworks Hooks in Methoden ein, die sie nicht besitzen. Sie finden den richtigen Platz in einer kompilierten Methode und fügen einen Aufruf des Codes des Mods ein. Die Arbeit auf Bytecode-Ebene bedeutet, dass ein Mod sich an das Spiel anhängen kann, obwohl er nie eine Zeile des ursprünglichen Java-Codes sieht.

In der Praxis sieht das so aus, wie Terminus und andere Fabric-basierte Clients Minecraft verändern. Anstatt von Minecrafts Quellcode zu verfügen, arbeiten sie mit den kompilierten .class-Dateien und schreiben den Bytecode um, um Funktionen wie Module, Einstellungsbildschirme und benutzerdefinierte Renderings hinzuzufügen.

Wo ASM ins Spiel kommt

ASM ist eine Java-Bibliothek zum Lesen, Schreiben und Transformieren von Bytecode. Sie parst eine .class-Datei in Ereignisse oder einen Baum, ermöglicht die Änderung der Anweisungen und gibt eine gültige .class-Datei aus. Sie ist klein, schnell und arbeitet nahe am Rohformat, weshalb höherwertige Modding-Tools darauf aufbauen, anstatt die Klassendateien selbst zu parsen.

Die meisten Modder rufen ASM nie direkt auf. Sie verwenden eine höherwertige Schicht, die die Opcodes verbirgt und eine freundlichere Möglichkeit bietet, zu sagen: "Führe meinen Code am Anfang dieser Methode aus." Darunter wird Bytecode bearbeitet, oft über ASM.

Dekompilierung von Bytecode zurück zum Quellcode

Dekompiler wie Fernflower und CFR nehmen kompilierte .class-Dateien entgegen und rekonstruieren daraus lesbaren Java-Code aus dem Bytecode. Der Prozess ist nicht perfekt: einige Namen und Kommentare gehen verloren, manche Konstrukte sind schwieriger wiederherzustellen als andere, und verschleierter Code wird unlesbar. Aber bei gewöhnlichem, unverdecktem Code erzeugen Dekompiler in der Regel eine Ausgabe, die dem Original nahe genug ist, um sie zu verstehen und damit zu arbeiten.

Dies ist nützlich, um zu erlernen, wie das Spiel funktioniert, ersetzt aber nicht den Besitz des tatsächlichen Quellcodes. Der dekompilierte Code ist immer noch kompilierte Logik ohne Kommentare, und rekonstruierte Variablennamen können irreführend sein. Es ist ein Lesewerkzeug, kein Schreibwerkzeug.

FAQ