Was ist neu in Java 7? Teil 3 – Allgemeingültigkeit

Sprachen  –  0 Kommentare

Nach den ersten beiden Teilen der Artikelserie zu den Neuerungen von Java 7 geht dieser Beitrag auf die Änderungen der Laufzeitumgebung ein. Es stehen jedoch weniger die Erweiterungen und Änderungen an der Sprache selbst, sondern vermehrt der Einsatz der JVM als Laufzeitumgebung für andere Programmiersprachen im Vordergrund.

Die Java Virtual Machine (JVM) ist der Teil der Java-Laufzeitumgebung (Java Runtime Environment) für Programme, der für die Ausführung des Java-Bytecodes verantwortlich ist. Sie dient als Schnittstelle zur Maschine und zum Betriebssystem und ist für die meisten Systeme (Desktop, Server oder Mobile) verfügbar. Die bekannteste und wohl verbreitetste ist die HotSpot JVM, die auch Teil des Java-7-Downloads ist. Im Hause Oracle gibt es zudem noch das von BEA Systems übernommene JRockit. Es liegt derzeit aber noch nicht in mit Java 7 kompatibler Version vor, und ob Oracle diese überhaupt veröffentlichen wird, ist fraglich, da der Java-Statthalter angekündigt hat, beide Produkte zu vereinen.

Was ist neu in Java 7?

Die Artikel zur Reihe:

Von Beginn an war die JVM nicht darauf beschränkt, nur Java-Applikationen auszuführen. Der von ihr ausführbare Bytecode ist Java-unabhängig und kann von nahezu beliebigen Programmiersprachen stammen,
solange diese einen geeigneten Bytecode-Übersetzer (Compiler) haben. Beim Bytecode handelt es sich um eine maschinenunabhängige Sprache, die die JVM in ausführbaren Maschinencode der jeweiligen Zielplattform umwandelt. Das geschieht "gerade rechtzeitig" ("just in time", JIT). Der finale Übersetzungsvorgang schlägt sich also in der Laufzeit des Programms nieder.

Am Beginn der Programmlaufzeit wird der Bytecode tatsächlich lediglich interpretiert, und erst Schritt für Schritt entscheidet sich die JVM dafür, besonders aufwendige Methoden (Hotspots) in Maschinencode zu übersetzen. Um zu hohe negative Laufzeitaspekte zu vermeiden, wenden die JVMs darüber hinaus unterschiedliche Optimierungsstrategien an, die anhand diverser Parameter den Übersetzungsvorgang und auch anschließende dynamische Optimierungen zur Laufzeit steuern.

Im Laufe der vergangenen zehn Jahre hat das zu einer performanten, plattformunabhängigen, weit verbreiteten und auf Nebenläufigkeit optimierten Laufzeitumgebung geführt. Diese Eigenheiten der JVM haben auch bei Entwicklern anderer Programmiersprachen Begehrlichkeiten geweckt. Vor allem Ruby und Python haben früh den Sprung auf die JVM gewagt, und es entstanden die Implementierungen JRuby und Jython.

Größtes Manko war aber, dass die Vielzahl der Optimierungen der JVM auf die Sprache Java abgestimmt war. Unterschieden sich die anderen Programmiersprachen deutlich, beispielsweise in der Art, wie Mechanismen zur Auflösung von Objekten oder Methoden funktionieren, war die JVM nicht immer die optimale Ausführungsumgebung. Besonders komplex wurde es, wenn es sich um unterschiedliche Typsysteme handelte, denn Java ist eine Sprache, die statische Typisierung zur Übersetzungszeit verwendet. Deshalb war die JVM für die Ausführung statisch typisierter Sprachen optimiert.

Bei statischer Typisierung wird im Gegensatz zu dynamischer der Datentyp von Variablen und anderen Programmbausteinen während der Kompilierung festgelegt. Ruby und Python sind (zumindest teilweise) dynamisch typisiert, und der Programmcode enthält vielfach keine Informationen über Datentypen zur Übersetzungszeit. Sie lassen sich nur zur Laufzeit bestimmen. Diese Eigenart der JVM war bis dato sicherlich gewollt und hat auch zur Verbreitung und zum allgemeinen Erfolg der Programmiersprache Java beigetragen. Das hat aber die Compiler-Entwicklung für dynamisch typisierte Sprachen deutlich erschwert.

Die Herausforderung

Ein einfaches Beispiel, geschrieben in einer fiktiven Programmiersprache, soll die Schwierigkeit bei der Übersetzung dynamisch typisierter Sprachen verdeutlichen:

def addiereZwei(a, b)
a+ b
end

Das Verhalten des Additions-Operators "+" unterscheidet sich je nach eingesetzter Typisierung. Bei einer statisch typisierten Sprache entscheiden die Datentypen a und b über die tatsächliche Implementierung. Der Java-Compiler würde ein "+" auf zwei Integer-Typen mit der JVM-Instruktion "iadd" übersetzen:

aload_0       
invokevirtual #6 // Method java/lang/Integer.intValue:()I
aload_1
invokevirtual #6 // Method java/lang/Integer.intValue:()I
iadd

Im Gegensatz dazu muss ein Übersetzer für eine dynamische Sprache die Auflösung der Operator-Methode bis zur Laufzeit verzögern. Ein entsprechender Aufruf müsste lauten:

call +(a,b)

Dabei muss die Laufzeitumgebung herausfinden können, um welche Typen es sich bei den Variablen a und b handelt und eine für diese Typen geeignete Implementierung der Additions-Methode "+" verwenden. Einfach alle Variablen so behandeln, als wären es Objekte, funktioniert nicht, da java.lang.Object keine "+"-Operation kennt.