Läuft überall: Die Java Virtual Machine im Überblick

Seite 2: Stapelweises Verarbeiten

Inhaltsverzeichnis

Die JVM ist eine Stack-orientierte Maschine. Das bedeutet, dass sie keine Register hat, sondern die für eine Operation benötigten Werte auf einem Stack (Stapel) ablegt und sie durch das Ergebnis austauscht. Das System berechnet beispielsweise den Ausdruck

c = a + b;

für ganzzahlige int, indem es zunächst den Wert von a aus dem lokalen Speicher lädt und auf dem Stack ablegt. Anschließend lädt es den Wert von b und legt es auf dem Stack ab. Schließlich ruft es die für int zuständige Addition auf, die die beiden zuoberst liegenden int entfernt und sie durch ihre Summe ersetzt. Abschließend wird diese Summe vom Stack entfernt und im lokalen Speicher von c gespeichert. In der Code-Repräsentation werden die Namen für a, b und c durch nummerierte Speicherplätze ersetzt. Wenn die drei Variablen auf den Plätzen 1, 2 und 3 liegen, sieht der Code für obige Anweisung folgendermaßen aus:

iload_1
iload_2
iadd
istore_3

Einen Stack zu benutzen ist in vielerlei Hinsicht praktisch. Insbesondere lässt sich damit eine virtuelle Maschine nahezu trivial und vor allem effizient implementieren, weil für die Ausführung nicht viel mehr als der Stack und der lokale Speicher benötigt werden. Die JVMS beschreibt das wie folgt:

do {
atomically calculate pc and fetch opcode at pc;
if (operands) fetch operands;
execute the action for the opcode;
} while (there is more to do);

Abbarbeiten der Addition (Abb. 1)

Zudem verlangt die JVM für jede Methode den exakt benötigten lokalen Speicher und die maximal benötigte Größe des Stacks, was Stack-Überläufe verhindert. Schließlich gibt es durch die Nummerierung der Speicherplätze keine Adressen (Pointer), die in anderen Sprachen zu überraschenden Fehlern führen können.

Die auszuführenden Instruktionen legt das System einzeln in Bytes ab. Da der Bytecode extrem kurz ist, lässt er sich effizient über ein Netzwerk übertragen. Nur zum Vergleich (von Äpfeln und Birnen): Die Java-Version des klassischen "Hallo Welt!"-Programms, das nichts anderes tut, als ebendiese Zeichenkette auszugeben, benötigt (in Abhängigkeit von der Länge der Paket- und des Klassennamens) etwas mehr als ein halbes KByte (540 Byte). Das funktionsgleiche Programm in C belegt knapp 8 KByte (8432 Byte), von denen der Teil, der die eigentliche Anweisungen für die Ausgabe enthält, lediglich 768 Byte benötigt.

Der tatsächlich ausführbare Bytecode in Java nimmt übrigens deutlich weniger Platz ein: Der Code für die Initialisierung der Klasse benötigt lediglich 5 Byte und die Ausgabe der Zeichenkette 9 Byte. Der Rest enthält zusätzliche Informationen wie den Klassennamen, die Methodensignaturen, Dateinamen, Zeilennummern und Ausnahmen.

Mehr Infos

Java 2017

Mehr Informationen zu Java 9, Java EE 8 und aktuellen Entwicklungen im Java-Umfeld gibt es im iX-Developer-Sonderheft zum Thema, das unter anderem im heise Shop erhältlich ist.

Nebenbei bemerkt ist eine Java-Klassendatei eine effiziente Datenstruktur, die sämtliche Informationen über den sogenannten Constant Pool bereitstellt. Dabei werden alle Elemente variabler Größe als Attribute gespeichert, die sich über ihren Namen identifizieren lassen. Neben den für den Ablauf vordefinierten Attributen lässt sich eine Klasse so (auch für eigene Zwecke) beliebig erweitern.

Die Entscheidung, die Instruktionen in einzelnen Bytes abzulegen, hat jedoch auch Nachteile. Unter anderem ist der Befehlssatz dadurch auf "nur" 256 Instruktionen beschränkt. Des Weiteren müssen die Befehle oder deren Teile, die aus mehreren Bytes bestehen, in der Regel aus einzelnen Bytes zusammengesetzt werden, da die zugrunde liegende Hardware möglicherweise keine direkten Zugriffe auf Wörter erlaubt, die nicht an passenden Adressen ausgerichtet sind. Aber die dadurch entstehenden längeren Ausführungszeiten nimmt man billigend für die kleineren Klassendateien in Kauf.