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.
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:
| Opcode | Was er tut |
|---|---|
iload | Legt eine lokale Integer-Variable auf den Stapel |
iadd | Entfernt zwei Integers vom Stapel, legt ihre Summe ab |
invokevirtual | Ruft eine Instanzmethode auf |
getfield | Liest ein Instanzfeld |
return | Kehrt 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
Nein. Maschinencode sind native Anweisungen für eine bestimmte CPU. Bytecode ist der eigene Befehlssatz der JVM, der von der JVM zur Laufzeit in Maschinencode übersetzt wird. Diese zusätzliche Schicht ermöglicht es, dass eine .class-Datei auf jeder Plattform ausgeführt werden kann.
Teilweise. Dekompilierer rekonstruieren lesbaren Java-Code aus Bytecode, und sie kommen bei gewöhnlichem Code ziemlich nahe. Das Ergebnis ist nicht immer identisch mit dem Original, und Kommentare sowie einige lokale Namen fehlen, aber es reicht meistens aus, um dem Verlauf folgen zu können.
Da Mods normalerweise nicht den Quellcode haben. Sie liefern gegen das kompilierte Spiel aus. Das Neuschreiben von Bytecode ermöglicht es einem Mod, das Verhalten zu ändern, ohne jemals die ursprünglichen Java-Dateien anzufassen oder zu haben.
Normalerweise nicht. Modding-Frameworks und Skriptschichten übernehmen die Bytecode-Arbeit für Sie. Es hilft, wenn man weiß, wie es funktioniert, wenn man einen kniffligen Patch debuggt oder Werkzeuge entwickelt.