Bytecode ist eine kompakte, plattformunabhängige Zwischensprache, die nicht direkt vom Prozessor, sondern von einer virtuellen Maschine (VM) ausgeführt wird. Bekannte Beispiele sind der Java-Bytecode in .class-Dateien, der Python-Bytecode in .pyc-Dateien, die .NET-Zwischensprache CIL und WebAssembly im Browser.

Wie Bytecode entsteht

Ein Compiler (z.B. javac) übersetzt den Quelltext über die übliche Pipeline: lexikalische Analyse, Parser, AST, Zwischendarstellung und Codegenerierung. Statt Maschinencode für eine konkrete CPU erzeugt er Bytecode für die Ziel-VM. Bei interpretierten Sprachen wie Python passiert das automatisch beim ersten Import: Der Quelltext wird in Bytecode übersetzt und .pyc-Dateien werden als Cache gespeichert.

Stackmaschinencode

Die meisten Bytecode-Instruktionen arbeiten auf einem Operanden-Stack. Eine Addition sieht dann etwa so aus: iconst_3 legt die 3 auf den Stack, iconst_4 die 4, iadd nimmt beide, addiert sie und legt das Ergebnis zurück. Dadurch ist der Code sehr kompakt und lässt sich leicht verifizieren — Java prüft jede geladene Klasse mit einem Bytecode-Verifier auf sichere Typen und Speicherzugriffe.

Ausführung: Interpreter und JIT

Die VM kann Bytecode auf zwei Arten ausführen:

  • Interpretieren: Die VM liest jede Instruktion und führt sie direkt aus — einfach und ohne Startzeit, aber langsam.
  • JIT-Kompilierung: Die VM übersetzt heiß genutzte Bytecode-Abschnitte zur Laufzeit in echten Maschinencode (Just-in-Time). Das machen die JVM (HotSpot), .NET (JIT) und moderne JavaScript-Engines. Python nutzt standardmäßig nur den Interpreter.

Damit ist Bytecode die Grundlage für interpretierte Sprachen und für Plattformunabhängigkeit: Eine .class-Datei läuft auf jeder JVM — der Vorteil Write once, run anywhere. Die Runtime ist die VM samt Laufzeitbibliotheken, die den Bytecode ausführt.

Abgrenzung

Bytecode ist kein Maschinencode: Die CPU kann ihn nicht direkt ausführen, erst die VM übersetzt ihn (beim JIT) oder interpretiert ihn. Er ist auch keine generische Zwischendarstellung im Compiler-Sinne, sondern eine konkrete, gespeicherte Form mit festem Instruktionssatz, die als Datei verteilt wird. Klassische kompilierte Sprachen wie C erzeugen dagegen Objektdateien, die ein Linker zu einer ausführbaren Datei zusammenführt.

Beispiele in der Praxis

  • Java: javac erzeugt .class-Dateien, java startet die JVM.
  • Python: python -m py_compile datei.py erzeugt sichtbaren Bytecode; normalerweise liegen die .pyc-Dateien im __pycache__-Ordner.
  • .NET: C#-Compiler erzeugen DLLs mit CIL, die die Common Language Runtime (CLR) ausführt.
  • WebAssembly: .wasm-Module sind der Bytecode für die Browser-VM und laufen dort nahezu mit nativer Geschwindigkeit.