Eigentlich ist man als Java-Entwickler gewohnt, mehrere Jahre auf ein neues Java-Release zu warten und sich anschließend an ein paar großen und vielen kleinen Erweiterungen und Verbesserungen zu erfreuen. Doch mit dem Release von Java 9 hat Oracle auch die Release-Planung von Java verändert. Diese sieht vor, dass ab Java 9 alle sechs Monate ein neues Major Release von Java erscheint. Diese Änderung beschert uns nun also jetzt, im März 2018, bereits Java 10.
Natürlich kann ein solches Release nicht mehr dieselbe Größe erreichen wie die bisherigen Releases. Der Vorteil liegt eher darin, konstant Neuerungen zu erhalten und nicht auf triviale kleine Verbesserungen lange warten zu müssen.
Im Folgenden betrachten wir zuerst die Neuerungen, die über Java Enhancement Proposals (JEPs) umgesetzt wurden. Anschließend schauen wir uns weitere kleine Änderungen an. Zum Schluss wage ich dann einen Ausblick auf Dinge, die möglicherweise in Java 11 oder später erscheinen könnten.
Local-Variable Type Inference
Java 10 besteht insgesamt aus zwölf neuen JEPs. Der für die meisten Entwickler wohl interessanteste ist 286.
Java gilt für viele als sehr geschwätzige Sprache. So muss an vielen Stellen der
Typ auf der linken und auf der rechten Seite beschrieben werden, zum Beispiel
bei der Erzeugung eines neuen Objektes. An einigen Stellen ist es nun möglich,
diesen auf der linken Seite durch das Keyword var
zu ersetzen und sich somit
ein paar Zeichen und die Wiederholung der Typinformation zu sparen. Aus
MyObject o = new Object();
wird somit var o = new MyObject();
.
Diese Syntax kann jedoch nicht an allen Stellen genutzt werden, sondern wurde
auf drei Fälle begrenzt. Der erste Fall bezieht sich auf lokale, also innerhalb
einer Methode deklarierte Variablen. Hierbei kann var
genutzt werden, solange
der Variablen auch direkt ein Wert zugewiesen wird. Ein einfaches var foo;
ohne Zuweisung ist also nicht möglich. Und selbst bei einer direkten Zuweisung
gibt es Fälle, die nicht funktionieren, wie beispielsweise die Zuweisung einer
Lambda-Funktion oder Methodenreferenz.
Die beiden anderen Fälle beziehen sich auf for-Schleifen. Hierbei kann sowohl
für die Variable der for-each-Schleife als auch für die Zählvariable der
normalen for-Schleife var
genutzt werden.
Auf die Einführung eines zweiten Keywords, um Unveränderlichkeit anzuzeigen, wie
const
in JavaScript oder val
in Kotlin, wurde bewusst verzichtet und es muss
weiterhin auf die Verwendung von final zurückgegriffen werden. Eine
unveränderliche lokale Variable kann also mit final var
deklariert werden.
JDK Repository Konsolidierung
Innerhalb von JEP 296 wurde dafür gesorgt, dass sich das gesamte JDK nun innerhalb eines Mercurial Repositories befindet. Bisher war das JDK auf insgesamt acht verschiedene Repositories aufgeteilt: root, corba, hotspot, jaxp, jaxws, jdk, langtools und nashorn. Zwar war die Isolation der teilweise sehr verschiedenen Module gut, es brachte aber auch Probleme mit sich. Einerseits waren Bugfixes schwerer zu tracken, wenn mehrere Module geändert werden mussten, da man diese Bugfixes nicht mit einem modularen Commit machen konnte. Andererseits erleichtert diese Konsolidierung das spiegeln des JDKs nach Git und somit möglicherweise auch nach GitHub.
Änderungen beim Garbage Collector
JDK10 enthält zwei JEPs zum Thema Garbage Collector (GC). In JEP 304 ging es darum, ein sauberes Interface für einen GC zu definieren und den Build-Prozess von diesem zu erneuern. Bisher lagen die Quelldateien für einen GC quer verstreut im JDK und es war somit für Neulinge schwer, sich zurechtzufinden. Zudem war es nicht einfach möglich, bei einem Build des JDKs bestimmte GCs zu exkludieren. All dies ist nun dank der Umstrukturierung möglich.
Die zweite Änderung zum Thema GC findet man in JEP 307. Dieser ermöglicht es, dass der in JDK9 zum Standard deklarierte G1 (Garbage-First) GC im Falle einer vollen Collection parallel läuft. Ziel ist es, dass diese genauso schnell ist wie im vorherigen Default, dem Parallel Collector.
Startzeit und Speicherverbrauch
Der JEP 310 beschäftigt sich mit einem bereits seit Java 5 verfügbarem Feature, dem sogenannten Class-Data Sharing (CDS). Dieses erlaubt es der JVM, bestimmte Klassen in ein geteiltes Archiv zu legen. Dieses Archiv kann nun beim nächsten Start der JVM direkt in den Arbeitsspeicher gemappt werden und ermöglicht somit einen schnelleren Start. Dieses Feature war bis JDK9 alleine dem bootstrap Classloader vorbehalten und wird nun mit JDK10 auch allen anderen Classloadern ermöglicht.
Neben einer schnelleren Startzeit ermöglicht das CDS es, auch ein solches Archiv zwischen mehreren JVM-Prozessen auf einer Maschine zu teilen. Somit kann Arbeitsspeicher eingespart werden. Im JEP selbst beschreibt Oracle, dass bei einem Applikationsserver, der sechs JVM-Prozesse nutzt und insgesamt 13 GB an Arbeitsspeicher verbraucht, ca. 340 MB eingespart werden können.
Ein neuer JIT Compiler
Java nutzt zur Laufzeit einen Just-In-Time (JIT) Compiler, um Code, der häufig ausgeführt wird, nicht mehr im Bytecode auszuführen, sondern diesen in nativen Maschinencode zu übersetzen und anschließend diesen zu nutzen. Dies führt zu deutlichen Performanzgewinnen. Bereits im JDK9 wurde im Rahmen von JEP 295 ein in Java geschriebener Ahead-Of-Time (AOT) Compiler entwickelt. Dieser führt die Übersetzung in nativen Maschinencode nicht während der Laufzeit, sondern zuvor durch und kann somit nicht alle Optimierungen eines JIT durchführen, da einige Informationen erst zur Laufzeit vorhanden sind. Dieser, Graal genannte, Compiler kann nun dank JEP 317 auch experimentell als JIT genutzt werden. Da es allerdings nicht Ziel war, dieselbe Performanz wie mit den bisherigen JITs zu erreichen, sondern es eher darum geht zu experimentieren, was mit einem in Java geschriebenen JIT möglich ist, ist von einem Einsatz aktuell noch abzuraten.
Neues Versionierungsschema
Erst mit JDK9 hatte Oracle innerhalb des JEP 223 das Versionierungsschema standardisiert. Mit der Änderung der Release-Planung auf die nun sechsmonatigen Veröffentlichungen des JDKs wollte Oracle hier allerdings noch einmal nachbessern. Anfangs stand sogar die Idee im Raum, auf 9 nicht 10, sondern 18.3 folgen zu lassen. Dies wurde jedoch nach Protesten der Community fallen gelassen. Trotzdem sah Oracle noch Nachholbedarf und dieser wurde nun in JEP 332 umgesetzt.
Im Grunde bleibt das alte Schema bestehen, es ergeben sich jedoch leichte
semantische Unterschiede. Die Versionsnummer besteht nun aus den vier
Bestandteilen $FEATURE.$INTERIM.$UPDATE.$PATCH
.
Feature wird dabei für jedes Feature-Release um eins erhöht. Ein solches Release darf sowohl neue Features hinzufügen als auch alte entfernen, sofern diese Entfernung im vorherigen Feature-Release angekündigt wurde. Mit der aktuellen Planung von sechs Monaten ergibt sich somit, das im September JDK11 folgt.
Der Interim-Teil wird für Releases genutzt, die keine neuen Features bringen,
sondern nur kompatible Bug-Fixes und Erweiterungen enthalten. Aktuell wird
dieser Teil nicht genutzt und ist somit immer 0
.
Update wiederum wird verwendet, um Releases zu erzeugen, die kompatibel sind und Security, Regressionen und/oder Bugs fixen. Aktuell erscheint ein solches Release geplant einen Monat nach einem Feature-Release und anschließend alle drei Monate. Somit sollte im April bereits das JDK 10.0.1.0 erscheinen.
Als letzte Stelle bietet Patch die Möglichkeit, im Falle größerer Probleme, die unmittelbar gelöst werden müssen, ungeplante Releases zu erzeugen.
Weitere JEPs
Komplettiert wird JDK10 durch weitere fünf kleinere JEPs. Mit Thread-Local Handshakes lassen sich einzelne Threads von der JVM einfacher stoppen.
JEP 313 entfernt javah
aus dem JDK. Dieses wurde genutzt, um beim
Einsatz von JNI Dateien mit den nativen Headern zu erzeugen. Als Ersatz kann die
bereits mit JDK8 in javac
eingebaute Option -h
genutzt werden.
Zudem werden dank JEP 314 nun zusätzliche Unicode-Language-Tag-Erweiterungen und mit JEP 316 alternative Speichermedien, wie NV-DIMM, für die Verwaltung des Heaps unterstützt.
Vervollständigt wird JDK10 durch eine Menge von Root-Zertifikaten. Bisher konnten diese nicht im OpenJDK ausgeliefert werden, da sie nicht unter einer Open-Source-Lizenz standen. Mit dem JEP 319 geht Oracle genau dieses Problem an und stellt die bisher im OracleJDK befindlichen Zertifikate zur Verfügung.
Weitere kleine Änderungen
Neben den großen Änderungen, die durch die JEPs umgesetzt werden, enthält ein JDK-Release auch eine Menge von kleineren Änderungen an den bestehenden APIs und Tools. Die folgenden Änderungen sind dabei nur eine Auswahl.
Führt man Java in Containern, zum Beispiel mit Docker, aus, so war es bisher so,
dass die JVM Quotas, die man einem Container gegeben hat, ignorierte. So konnte
man zwar dem Container sagen, dass er nur maximal 1 GB an Arbeitsspeicher zur
Verfügung hatte, die JVM versuchte jedoch trotzdem, den gesamten auf dem
Docker-Host zur Verfügung stehenden Arbeitsspeicher zu nutzen. Gleiches gilt für
die Limitierung von CPU. Auch diese Problematik wurde im JDK10 angegangen.
Nachvollziehen kann man die hierfür gemachten Änderungen und relevanten Flags
des java
-Tools in einem Ticket.
Weiterhin wurde Javadoc um ein neues Tag, @summary
, erweitert,
das es ermöglicht, gezielter auszuzeichnen, was man als Beschreibung in der
Methodenübersicht anzeigen möchte. Bisher wurde hierzu lediglich der erste Satz
des Javadoc-Kommentares verwendet.
Auch Collections und Streams wurden um eine Kleinigkeit erweitert. Nachdem
bereits im JDK9 die statischen Factory-Methoden eingeführt wurden, welche
unveränderliche Collections erzeugen, wurden nun weitere Methoden ergänzt, um
unveränderliche Datenstrukturen zu erzeugen. Bisher standen hierzu einige
Methoden in java.util.Collections
zur Verfügung. Diese erzeugen jedoch nur
eine unveränderliche Sicht, das heißt, ändert man anschließend die unterliegende
Datenstruktur, so ändert sich auch die so erzeugte Sicht. JDK10 fügt nun in den
Klassen java.util.Set
, java.util.Map
und java.util.List
jeweils eine
Methode copyOf
ein. Diese erzeugt aus einer vorhandenen Datenstruktur eine
neue unveränderliche, die sich auch nicht ändert, wenn man die vorhandene
anschließend modifiziert.
Damit man solche unveränderliche Datenstrukturen auch bei der Nutzung von
Streams erzeugen kann, wurden zusätzlich in der Klasse
java.util.stream.Collectors
die Methoden toUnmodifiableList
,
toUnmodifiableMap
und toUnmodifiableSet
hinzugefügt. Es ist somit nun
möglich, mit JDK-Bordmitteln bequem wirklich unveränderliche Datenstrukturen zu
erzeugen.
Zudem wurden die Klasse java.util.Optional
und die Varianten für Primitive,
java.util.OptionalDouble
, java.util.OptionalInt
und
java.util.OptionalLong
, um die Methode orElseThrow
ohne Argumente erweitert.
In diesem Falle wird bei einem leeren Optional eine NoSuchElementException
geworfen.
Zu guter Letzt wurde auch das API des sich weiterhin im Inkubator befindlichen neuen HTTP2-Clients verbessert und ist sicher einen Blick wert. Wer gerne noch weiter schauen möchte, was sich in den bestehenden APIs so geändert hat, sei auf das GitHub-Projekt von Gunnar Morling verwiesen.
Ausblick in die Zukunft
Die bereits erwähnte Entscheidung, alle sechs Monate ein neues Release zu veröffentlichen, führt dazu, dass wir bereits im September das nächste JDK begrüßen dürfen. Bisher (Stand Februar 2018) sind dort vier JEPs als potenzielle Kandidaten gelistet, darunter zum Beispiel JEP 320, um alte APIs aus dem JDK zu entfernen. Es lohnt sich ab und an mal zu schauen, welche neuen JEPs hinzugefügt wurden, um einen Überblick zu erhalten, auf was man sich freuen darf.
Neben diesen konkret geplanten JEPs wird auch im Hintergrund fleißig an weiteren spannenden Dingen gearbeitet. Besonders interessant sind hierbei die Entwicklungen im sogenannten Projekt Amber. Aus diesem Inkubator werden in Zukunft vermutlich Sprachfeatures wie erweiterte switch-Statements und Enums, Pattern-Matching und Data-Classes in die Sprache Java integriert werden.
Außerdem stehen Ideen im Raum, Strings zu erweitern, um diese als mehrzeiliges Literal definieren zu können, oder Java-Quelldateien, die keine Abhängigkeiten zu Bibliotheken haben, direkt starten zu können, ohne diese vorher kompilieren zu müssen.
Zudem werden natürlich auch die existierenden APIs erweitert und verbessert,
beispielsweise wird überlegt, java.lang.String
um eine Methode repeat
zu
erweitern.