Grundlegendes zur JVM und der Operandenspeicherung in Rechnern
Das Geheimnis der Plattformunabhängigkeit von Java, also der Fähigkeit, mit nur einem Kompilat auf Rechnern aller Architektur und ungeachtet des Betriebssystems zu laufen1, liegt bekanntermaßen in der JVM, der Java Virtual Machine. Wie der Name bereits verrät, ist sie eine virtuelle Maschine, also ein simulierter Rechner, der auf dem tatsächlichen Rechner läuft. Grundlegendes zur Funktionsweise der JVM und insbesondere zur Operandenspeicherung im Vergleich zu tatsächlichen Rechnern lesen Sie hier.
Operandenspeicherung allgemein
Die meisten Befehle, welche die CPU ausführen soll, benötigen einen oder mehrere Operanden. Sollen zwei Zahlen addiert werden, sind die beiden Summanden nötig, soll verzweigt werden, ist eine Sprungadresse nötig und soll ein Datum in den Hauptspeicher geladen werden, ist sowohl das Datum selbst als auch eine Zieladresse nötig. Für die Speicherung dieser Operanden gibt es in Rechnern im Wesentlichen drei verschiedene Architekturen:
- Per allgemeiner Register: Die CPU verfügt über eine gewisse Anzahl durchnummerierter allgemeiner Register in Wortgröße (üblicherweise 32 oder 64 Bit), in die Operanden geladen und Ergebnisse gespeichert werden,
- per Akkumulator: Die CPU verfügt über ein einziges Register, in das nur einer der beiden Operanden geladen wird2 und in welchem stets das Ergebnis platziert wird oder
- per Keller (Stack): Die CPU verfügt über einen Kellerspeicher, auf den die Operanden gelegt (push) werden. Es werden für Operationen die benötigte Anzahl Operanden von diesem Keller genommen (pop).
Grundlegendes zur JVM
Der Java-Compiler erzeugt keinen nativ ausführbaren Code, sondern
Java-Bytecode, der nicht direkt vom Rechner, sondern nur von der JVM
ausgeführt werden kann. Das Format ist ziemlich einfach, was unter
anderem an der Operandenspeicherung liegt, aber dazu gleich mehr. In der
JVM ist jedes Datum 32 Bit lang. Alle Daten, die größer sind, nämlich
Objekte und Arrays, liegen im Heap. Deren 32-Bit langen Adressen passen
wiederum perfekt dazu. Davon ausgenommen sind die Datentypen
long
und double
, die einfach zwei 32-Bit
Wörter umfassen.
Operandenspeicherung in der JVM
Jeder JVM-Befehl besteht aus einem 8 Bit langen Opcode, der
nur bei bestimmten Operationen von einem oder mehreren Bytes direkter
Operanden gefolgt ist. Die JVM verwendet nämlich den
Kellerspeicher für die Operanden, genannt operand
stack. Der vollständige Code einer Funktion, die zwei
int
s als Parameter hat und deren Summe zurückgibt, sieht
zum Beispiel so aus:
iload_0 // Stecke die lokale Variable 0 (der erste Parameter) in den Keller,
iload_1 // stecke die lokale Variable 1 (der zweite Parameter) in den Keller
iadd // addiere die beiden obersten Werte im Keller,
// stecke wiederum das Ergebnis in den Keller und
ireturn // gib den obersten Wert im Keller zurück.
Während es bei x86 etliche verschiedene Arten der Adressierung der Operanden und eine gute Handvoll Register für deren Speicherung gibt, lädt die JVM einfach alle Operanden in der richtigen Reihenfolge in den Keller und speichert Ergebnisse auch direkt dort. Das ist nicht nur für den Compiler sehr einfach, sondern lässt sich einheitlich auf allen möglichen Plattformen umsetzen, denn: Damit Java auf einer Plattform läuft, muss erst mal die JVM dort laufen. Die Architektur der JVM ist hinsichtlich der Operandenspeicherung einfach (und) genial.