Simon Koelsch coding software, using gadgets

2Mai/10Off

Java 7 Features

Update: Einige Features wurden aus Zeitgründen verschoben. Siehe hierzu "Java 7 - Plan B".

Da Java 7, Codename Dolphin, zum Ende des 2. Quartals mit dem letzten Milestone angekündigt ist, habe ich mir ein wenig Zeit genommen um die angekündigten Features vorzustellen. Ein Überblick über die zehn Milestones selbst ist auf der Projektseite zu finden.

Erweiterungen der Virtuellen Maschine

Diese Erweiterungen betreffen nur die Virtuelle Maschine HotSpot von Sun. Wer eine andere Lauftzeitumgebung nutzt, zum Beispiel die Netweaver Plattform oder die Apache Harmony Plattform, den betreffen diese Erweiterungen nicht.

G1

Offiziell verfügbar wird mit diesem Release eine neue Variante des Garbage Collectors, genannt G1 ("Garbage First"). G1 ersetzt damit die bisherige "Concurrent Mark-Sweep" (CMS) Methodik von HotSpot.
Oberflächlich erklärt geht CMS so vor, dass der Speicher in verschiedene Bereiche eingeteilt wird. Diese Speicherbereiche enthalten die Objekte je nachdem wie lange ihre Lebensdauer ist. Es existiert ein Bereich für sehr langlebige Objekte, für kurzlebige Objekte, etc. Dabei wird vom Garbage Collector der Speicher durchlaufen und alle nichtmehr referenzierte Objekte werden gelöscht.
Da der Speicher mit der Zeit fragmentiert, muss dieser irgendwann defragmentiert werden, indem die Objekte im Speicher umkopiert werden. Genau hierfuer muss die VM für einen kurzen Moment angehalten werden, was Performance kostet.
Ziel von G1 ist es, genau diese Phasen zu reduzieren. Dabei wird davon ausgegangen, dass je länger ein Objekt "lebt", desto wahrscheinlicher ist, dass es nicht mehr referenziert wird und der Speicher freigegeben werden kann. Je weniger Objekte am Ende existieren, desto kürzer dauert das Kopieren der noch verbleibenden Objekte und desto langsamer fragmentiert der Speicher.G1 vs CMS
G1 teilt dafür den Speicher in 1MB Bereiche ein und versucht vorauszusagen, wann die Objekte in diesen Bereichen ihre Lebenszeit überschritten haben. Den Objekten wird also genug Zeit zum "sterben" gegeben.
Eine ausführliche Erläuterung des Algorithmus findet sich im Paper "Garbage First Garbage Collection" (pdf) von Sun.
"Garbage First" ist übrigends schon seit Java 6 Update 14 als Backport enthalten und kann mit folgenden VM Parametern aktiviert werden:

-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

InvokeDynamic

Die HotSpot VM ist seit längerer Zeit ja nicht nur eine Plattform für Java Anwendungen. Die VM interpretiert in erster Linie Bytecode und dieser kann, wie bei Microsofts .NET, von unterschiedlichen Sprachen kommen. In diesem Umfeld wären da JRuby, Jython, Scala oder Groovy zu nennen. Problem bisher ist allerdings, dass Java eine typisierte Sprache ist, es wird also schon vor dem Übersetzen der Anwendung festgelegt, von welchem Datentyp die benutzten Variablen sind. Sprachen wie beispielsweise Jython sind allerdings nicht typisiert. Hier werden die Datentypen dynamisch verwaltet. Bisher gibt dafür keine Unterstützung im Bytecode auf VM Ebene, was zu lasten der Perfomance geht. Das bei Java 7 enthaltene JSR 292 führt im Bytecode ein "InvokeDynamic" ein. Dadurch sollte die Performance von nichttypisierten Sprachen in der VM verbessert werden. Weitere Infos sind im "Da-Vinci Machine" Projekt zu finden.

Komprimierte 64-Bit Pointer

Innerhalb des VM Speichers gibt es OOPs, sogenannte "Ordinary Object Pointer". Diese Pointer zeigen auf beliebige Objekte im Speicher. Dabei ist Objekt nicht im Sinne der objektorientierten Porgrammierung zu verstehen, sondern kann auch ein Element eines Objekts sein (zum Beispiel Instanzvariablen, Objektmetadaten, etc.). Pointer sind auf einem 64-Bit System normalerweise auch 64-Bit gross. Durch das hinzuaddieren einer fixen Speicheradresse kann die größe der Zeiger auch auf einem 64-Bit System auf nur 32-Bit reduziert werden. Dadurch wird der Speicherverbrauch und die Performance verbessert. Wer wissen möchte, wann genau die Pointer komprimiert werden und wie diese Komprimierung genau funktioniert findet im Sun Wiki eine ausführliche Erklärung mit Codebeispielen.

Modularisierung

Da das JDK und Java Anwendungen selbst immer größer und komplexer werden, gibt es schon lange Bestrebungen eine Schnittstelle und ein System zur Modularisierung der Komponenten zu schaffen. Um dieses System konsequent umzusetzen sind direkte Änderungen an den VM Spezifikation und der Sprache notwendig.
Mit dem JSR 294 werden diese notwendigen Änderungen in den Standard einfliessen.

Als Referenzimplementierung wird Sun ausserdem das Jigsaw Projekt bei seinen VMs hinzufügen. Ziel dieses Projekts ist zum einen, ein einfaches "low-level" Modulsystem zur Verfügung zu stellen, zum anderen um damit das JDK selbst zu modularisieren.

Wer jetzt an das schon jahrelang von der Industrie vorangetriebene OSGI denkt, liegt hier richtig. OSGI hat den gleichen Anspruch, setzt allerdings auf Java SE auf und ist nicht auf Sprach/VM-Ebene verankert. Was letztendlich das bessere Framework ist, darüber streiten sich die Java-Apostel. Auch ob OSGI und Jigsaw überhaupt in einem Widerspruch zueinander stehen. Sun selbst hat bisher in beide Systeme investiert und diese gefördert.

Sprachänderungen

Type Annotations

Die durch das JSR 308 vorgeschlagenen sogenannten "Type Annotations" sind inzwischen schon seit dem M4 Milestone im javac Compiler enthalten. Damit wird es möglich Annotationen nicht nur für Klassen und Methoden festzulegen, sondern für alle Datentypen. Dadurch wird zum Beispiel folgender Code zulässig:

List<@NonNull Object> foobar

Wer seinen Code trotzdem kompatibel zu älteren Java Version halten will, muss allerdings den javac Kompiler des "Type-Annotation" Projekts benutzen. Der lässt nämlich auch folgenden Code zu:

  List<!--*@NonNull*/ String--> strings;

Der OpenJDK Compiler wird hier die Annotation ignorieren, der des "Typ Annotation" Projekts nicht.

Project Coin

Mehrere kleine Sprachänderungen sind im "Project Coin" zusammengefasst.
Darunter fällt, dass man nun beim Switch-Statement Strings verwenden kann. Ein weiteres Feature wird automatisches Ressourcen Management sein. Wer beispielsweise mit Streams arbeitet, muss diese bisher explizit über eine entsprechende Methode schliessen. Beim automatischen Ressourcen Management wird der Stream dann in einen Try-Block eingeschlossen und beim verlassen dieses Blocks automatisch frei gegeben:

try (InputStream in = new FileInputStream(src);
    OutputStream out = new FileOutputStream(dest)) {
    byte[] buf = new byte[8192];
    int n;
    while ((n = in.read(buf)) &gt;= 0)
        out.write(buf, 0, n);
}

Einfacher wird auch die Arbeit bei Zeichenfolgen mit Trennzeichen. Möglich wird einen Unterstrich in numerischen Datentypen zu nutzen:

int phone = 040_1234567_123;

Auch die Verwendung von Literalen in Collections verspricht Code weiter lesbarer zu gestalten. Damit kann eine Map im Stil eines Arrays initialisiert werden:

final Map PSE = { 1 : "H",
    2 : "HE", 3 : "LI",
    // ...
};

Die Initialisierung bezieht sich auf Collections, welche dann auch nicht mehr verändert werden können.
Gegenüber irgendwelchen statischen Codeblöcken die eine Map mit ihrer put-Methode initialisiert ist diese Version sicherlich einfacher.

Eingereicht wurden für das Project Coin über 60 Erweiterungsvorschläge, wovon es 9 ins JDK 7 geschafft haben. Eine vollständige Liste ist im Projektwiki zu finden.
Ich persönlich hätte mir ein vereinfachtes Exception-Handling gewünscht:

try {
// ...
} catch (fooException|barException ex) {
//...
}

Dieses Proposal hat es leider nicht ins JDK geschafft. Vielleicht klappts ja mit dem JDK 8.
Scheinbar ist man sich da noch nicht so wirklich sicher... Joe Darcy hat im Februar nochmal auf der Projektmailingliste bestätigt, dass wohl überlegt wird das Feature doch ins JDK 7 zu übernehmen. Bleibt wohl nur abwarten...

Project Lambda - Closures in Java

Lamda ist wohl das bisher am meisten diskutierte Java 7 Feature. 2008 wurde angekündigt, Closures wären jetzt doch nicht in der neuen Java Version vorhanden, 2009 wurde diese Ankündigung dann wieder zurückgezogen.

Closures sind im Prinzip das Zusammenspiel von zwei Sprachfeatures. Dabei benutzt man anonyme Funktionen um auf Variablen ausserhalb ihrer lexikalischen Sichtbarkeit zuzugreifen. Diese anonyme Funktionen werden Lamdas genannt. Closures sind eine weit verbreitete Technik und werden in anderen Sprachen wie zum Beispiel Ruby, Perl, Lisp und JavaScript häufig genutzt.

Ein Codebeispiel in JavaScript macht diese Erklärung sicherlich ein wenig verständlicher:

function foo() {
    var str = "bar";
    return function() {
        return str;
    }
 
    var myFoo = foo();
    alert(myFoo());
}

Die Variable str ist an der Stelle, an der sich der Aufruf von alert befindet, nicht im lexikalischen Bereich verfügbar. Trotzdem kann über eine anonyme Funktion darauf zugegriffen werden.

Hilfreich können Closures vor allem an solchen Stellen sein, wo häufig anonyme innere Klassen erzeugt werden, nur um eine einzige Methode zu benutzen. Wer an GUI Entwicklung mit Swing denkt, kann sich bestimmt vorstellen wie hilfreich Closures hier beim Eventhandling sein können.

Closures sind definitv geplant. Die endgültige Syntax steht allerdings noch nicht fest. Da aber der nächste Milestone näher rückt und es bis Herbst nicht mehr lange hin ist, wird man sich wohl in den nächsten Wochen auf eine Syntax festlegen müssen.
Wer am Entwicklungsprozess näher interessiert ist, der sollte einen Blick ins lambda-dev Mailinglistenarchiv des Projekts werfen.

Core

Fork And Join

Mit dem JSR166y wird das Fork and Join Framework von Doug Lea zum Teil des Java Standards. Um mehrere CPUs effizient nutzen zu können, ist es wichtig seine Anwendung entsprechend zu gestalten. Java bietet dazu den Threading-Mechanismus. Das Problem an Threads ist ein gewisser Overhead bei der Verwaltung. Threads müssen erstellet werden, pausiert, sind geblockt weil sie auf andere Threads warten, etc. Dieser Overhead ist unter umständen grösser als die eigentlich zu berechnende Aufgabe.

Bei der Fork/Join Technik geht es darum ein Problem in viele kleine Probleme zu zerlegen, die völlig unabhängig voneinander gelöst werden können. Anschliessend werden die Teilergebnisse zur Lösung zusammengefasst. Klassisches Teile- und Hersche Prinzip.
Gerade hier sind Threads aber als einziger Mechanismus in Java zu unflexibel um viele kleine Teilprobleme zu lösen. Probleme im Ablauf die Mechanismen wie Locking, etc. erforden, tauchen durch die unabhängigkeit der Teilprobleme voneinander garnicht auf.

Mit dem JSR166y wird ein flexiblerer Mechanismus zur Verfügung gestellt. Hier erstellt man für jedes Teilproblem einen sogenannten "Task". Mehrere "Worker" arbeiten die so erstellten Tasks über eine Queue ab. Diese Queue unterstüzt sowohl das klassische Push/Pop für LIFO als auch take für FIFO.
Normalerweise wird ein Worker nach dem LFIO Prinzip seine Tasks abarbeiten. Sind alle Tasks berechnet, gibt es sogenanntes "Work-Stealing". Dabei wird ein weiterer Task aus der Queue eines zufällig gewählten anderen Workers abgearbeitet, allerdings nach dem FIFO Prinzip. Da "ältere" Tasks wahrscheinlich grössere Probleme enthalten und oftmals wieder in kleinere Tasks zerlegt werden können, wird hierdurch der Verwaltungsaufwand immer wieder neue Tasks zu "stehlen" weiter minimiert.
Lea hat mit seinem Framework Benchmarks für verschiedene mathematische Algorithmen durchgeführt und dabei ist durchaus ein deutlicher Unterschied zu anderen üblichen Multithreading-Methoden zu sehen. Eine Einführung in das Framework, Fork and Join Prinzip und die Benchmarks sind in seinem Paper "A Java Fork/Join Framework" (pdf) zu finden.

NIO.2

Java bietet mit seinem I/O Paket eine Reihe von rudimentären Eingabe und Ausgabemechanismen wie Streams, Serialisierung und Zugriff auf das Dateisystem. Beim Release der 1.4er API wurde die Performance dieser Mechanismen verbessert und neue Features hinzugefügt. Diese Erweiterung umfasst beispielswiese Hilfsmittel für Zeichensatz Encoding/Decoding, Sprachsupport für Reguläre Ausdrücke, Channels, etc. Zusammengefasst ist diese Neue I/O API unter Java NIO.

Im JDK 7 wird das JSR 203 umgesetzt: "More New I-O APIs for the Java Platform", kurz NIO.2.
Die Ziele von NIO.2 liegen dabei auf 3 Punkten:
Ein neues verbessertes Interface für das Dateisystem, lösen von Abhängigkeiten der Socket Channel Klassen und Unterstützung für asynchronen I/O Zugriff.

Für diese Änderungen war es eigentlich schon seit langem Zeit. Eine hervoragende Einführung in NIO.2 ist über Google Tech Talks auf YouTube verfügbar. Der Vortrag geht eine knappe Stunde und wird von Alan Bateman (Projektleiter für NIO.2) und Carl Quinn (Google) auf der JavaOne 2008 gehalten: The New NIO, aka JSR-203

Ausserdem ist im letzten Jahr ein Artikel welcher das neue Dateisystem Interface beleuchtet im Sun Developer Network erschienen: The Java NIO.2 File System in JDK 7

Classloader Änderungen

Sicherlich lädt nicht jeder Java Entwickler mit dem URLClassLoader seine Klassen selbst oder schreibt einen eigenen Classloader. Hier sind allerdings mit der Zeit unter anderem zwei Probleme aufgetaucht die in der neuen JDK Version gelöst werden:

In Java ist es nicht möglich zu bestimmen, wann der Garbage Collector nicht mehr referenzierte Ressourcen freigibt. Gerade bei zum Beispiel .jar Files die über den URLClassLoader geöffnet wurden, kann das in Kombination mit dem Windows Dateisystem Locking zu Problemen führen. Es lässt sich nicht sicherstellen, dass der Zugriff auf die geladenen Ressourcen zeitnah beendet wird. Der URLClassLoader implementiert nun aber das Closeable Interface. Hier können durch die Methode close() explizit der URLClassLoader und alle von ihm referenzierten Ressourcen geschlossen werden.

Beim Laden von neuen Klassen muss ein Classloader zuerst prüfen, ob eine Klasse nicht bereits geladen wurde. Wenn nicht, kann er diese innerhalb der VM definieren. Im Hinblick auf Multithreading und mehrere Classloadern innerhalb einer VM können sehr schnell komplexe und schwierig zu debuggende Probleme entstehen. Aus diesem Grund sind die meisten Methoden auf das Classloader Lock "synchronized". Dadurch wird aber unter bestimmten Voraussetzungen ein klassisches Deadlock erzeugt.
Um dieser Problematik entgegenzuwirken wurden einige Änderungen an java.lang.ClassLoader vorgenommen. Unter anderem steht nun eine Methode registerAsParallelCapable() zur Verfügung, um mit einem Classloader explizit seine threadsafe Eigenschaft zu signaliseren.
Alle Änderungen sind im JDK 7 Classloader Proposal sehr transparent und übersichtlich erklärt. Hier wurden ebenfalls die verschiedenen Lösungsvorschläge und Vorgehensweise um das Problem zu beheben ausführlich dargestellt.

Updates für Solaris

Im OpenJDK gibt es auch zwei implementierungsabhängige APIs für Solaris: Es wird Support für die mehr oder weniger populären SCTP und SDP Protokolle geben.

Graphik

Bisher gibt es bei Java verschiedene Möglichkeiten Graphik zu rendern. Das Rendern über Software ist in der Regel deutlich langsamer, als wenn die Graphikhardware des Rechners benutzt wird. Dafür gibt es dann sogenannte Rendering Pipelines. Direct3D ist nur auf Windows Systemen verfügbar, für alle anderen Systeme gibt es OpenGL. Unter Systemen mit X11 ist das X11 eigene XRendering inzwischen allerdings fast Standard und auf einer größeren Bandbreite an Hardware verfügbar. In Java 7 wird es dann für Java2D eine eigene XRender Pipeline geben.

Sonstiges

Natürlich wird auch der XML Stack einem Update unterzogen, dass bedeutet die JAXP, JAXB, und JAX-WS APIs werden in der letzten stabilen Version vorliegen.
Das schon seit einer Weile enthaltene Swing Look&Feel Nimbus wird jetzt auch offizieller Standard.
Swing wird ausserdem um den neuen "JXLayer" ergänzt. Dieser Layer bringt "hübsches" sperren von Komponenten mit sich (Webstart Beispiel), Maus Auto-Scrolling (Webstart Beispiel) und noch ein paar Spielerein mehr.
Weiterhin wird Java ab der 7er Version den Unicode Standard 5.1 unterstützen (aktuell ist 5.2) und das kryptografische Verfahren ECC (Elliptic-Curve-Cryptography) von Haus aus unterstützen.