Läuft überall: Die Java Virtual Machine im Überblick
Für viele ist die JVM nur das Programm, mit dem sich Java-Klassen ausführen lassen. Aber sie ist viel mehr: Sie ermöglicht, dass sich einmal erzeugte Java-Klassen auf diversen Hardwareplattformen nutzen lassen, ohne dass sie dabei Schaden anrichten können.
- Michael Wiedeking
Die Idee einer virtuellen Maschine ist wahrlich nicht neu. Beschreibungen und Implementierungen derartiger Systeme existieren bereits aus den 1960er Jahren. Einfach gesprochen definiert man zunächst ein abstraktes System, das alle gewünschten Eigenschaften besitzt, und übersetzt Quellcode derart, dass das resultierende Programm auf der virtuellen Maschine ablaufen kann. Alle, die das Programm nun laufen lassen wollen, müssen nur noch das abstrakte System für die konkrete Laufzeitumgebung implementieren.
Prominentes Beispiel ist vielleicht das UCSD p-System, das Mitte der 1970er Jahre an der University of California in San Diego (UCSD) als maschinenunabhängiges, portables (deswegen das p) Betriebssystem gestaltetet war und erlaube, p-Code ablaufen zu lassen. Damit konnten einmal übersetzte Pascal-Programme unverändert auf jeder unterstützten Hardware laufen und die dort verfügbare Peripherie nutzen. Das p-System war sogar neben CP/M und MS-DOS eines der Betriebssysteme, die auf IBMs PC laufen konnten.
Da Java ursprünglich für eingebettete Systeme wie Set-Top-Boxen gedacht war, lag es nah, mit einer virtuellen Maschine der Anzahl verschiedenster Prozessoren und Systeme Herr zu werden. Diese Entscheidung hat sich letztlich in vielerlei Hinsicht bewährt: Unter anderem ist Java gerade wegen seiner virtuellen Maschine spezifikationsgemäßer Bestandteil jedes Blue-ray-Spielers.
Write once, run anywhere!
Die Java Virtual Machine (JVM) ist eine solche abstrakte Maschine. Sie ist sorgfältig spezifiziert und kann garantieren, dass ein Java-Programm auf jeder JVM grundsätzlich das gleiche Ergebnis liefert, weil genau definiert ist, wie Java-Programme ablaufen und wie sich die Datentypen verhalten. Programmierer, die etwa mit C/C++ arbeiten müssen, können nur neidvoll auf die Java-Spezifikation blicken, die ganz konkret definiert, wie breit etwa ein int ist. Die C-Spezifikation enthält dagegen keine konkreten Aussagen über die Größe der verwendeten Datentypen, um auch wirklich jeden Prozessor unterstützen zu können. Diese Großzügigkeit führt bei vielen Dingen zu "implementation-defined", "undefined" oder gar zum "unspecified".
In einer zufällig zur Verfügung stehenden PDF-Version einer C-Spezifikation von 1997 kommt das Wort "undefined" knapp 170-mal, "implementation-defined" 140-mal und "unspecified" 70-mal vor. In der Java Virtual Machine Specification (JVMS) kommt "undefined" nur fünfmal, "unspecified" nur einmal und "implementation-defined" überhaupt nicht vor. In der Java Language Specification (JLS) kommt "undefined" sogar nur zweimal vor; "implementation-defined" immerhin noch einmal, allerdings in einem Beispiel, das Bezug auf C/C++ nimmt. Die JVM legt die Repräsentation der einzelnen Datentypen genau fest. Beispielsweise ist ein int ein vorzeichenbehafteter 32-Bit-Wert, der im Zweierkomplement repräsentiert ist. Damit ist alles – bis hin zur Position und Wertigkeit der einzelnen Bits – eindeutig spezifiziert.
Gleitkommazahlen stellen diesbezüglich eine Besonderheit dar: Während die Repräsentation dieser Zahlen auch eindeutig definiert ist, überlässt die JVM das Rechnen mit den Zahlen der zugrunde liegenden Hardware. Deshalb wurde mit der Version 1.2 ein Flag zur Annotation von Klassen eingeführt, womit garantiert werden kann, dass auf jeder JVM das exakt gleiche Ergebnis geliefert wird – ungeachtet einer deutlich schlechteren Performance, falls in diesem Fall ohne Unterstützung der Hardware gerechnet werden muss.
Darüber hinaus macht die JVM noch eine Reihe weiterer Zusicherungen. Sie gewährleistet unter anderem die Ausführung aller Operationen in der Reihenfolge, in der sie angegeben sind. Freilich steht es der JVM frei, die Operationen umzusortieren, solange es keine Auswirkungen auf Nebeneffekte hat. Führt die Kaskade der Methodenaufrufe
o.f1().f2().f3()
zu einer Exception in f2, ist garantiert, dass f1 vollständig ausgeführt und mit f3 auf keinen Fall angefangen wurde.
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.
Nicht zuletzt ist durch das Java Memory Model (JMM) genauestens spezifiziert, wie sich die JVM in einem nebenläufigen und parallelen Kontext verhält. Es beschreibt, welche Operationen atomar ablaufen und welche Sichtbarkeitsgarantien bei Mehrkern- oder Multiprozessorsystemen gewährt werden können.
Entwickler wissen somit jederzeit, was sie erwarten können und auf was sie achten müssen. Sie können alle Programme für die JVM problemlos auf jede andere JVM bringen. In diesem Zusammenhang sei jedoch erwähnt, dass die Umgebung, in der die JVM läuft, durchaus eine Rolle spielt. In Abhängigkeit vom Betriebssystem müssen Entwickler sorgfältig darauf achten, welche Annahmen sie beispielsweise über das Dateisystem oder die Benutzeroberfläche machen. Das betrifft genau genommen jedoch die Java-Bibliotheken und nicht die JVM.