Am 25. September 2018 war es soweit und das finale Release von Java 11 wurde veröffentlicht. Es ist das zweite Release in dem auf sechs Monate verkürzten Releasezyklus. Neben einigen neuen Features und vielen kleinen Änderungen sorgte vor allem die Diskussion um Support, Lizenzierung und die Verteilung von Builds des JDKs im Vorhinein für Unruhe.
Dieser Artikel stellt die insgesamt 17 neu umgesetzten JDK Enhancement Proposals, einige Änderungen und Erweiterungen an bestehenden Klassen und Interfaces sowie die Diskussion um das neue Support-Modell vor.
JEP | Name |
---|---|
181 | Nest-Based Access Control |
309 | Dynamic Class-File Constants |
315 | Improve Aarch64 Intrinsics |
318 | Epsilon: A No-Op Garbage Collector – Experimental |
320 | Remove the Java EE and CORBA Modules |
321 | HTTP Client – Standard |
323 | Local-Variable Syntax for Lambda Parameters |
324 | Key Agreement with Curve25519 and Curve448 |
327 | Unicode 10 |
328 | Flight Recorder |
329 | ChaCha20 and Poly1305 Cryptographic Algorithms |
330 | Launch Single-File Source-Code Programs |
331 | Low-Overhead Heap Profiling |
332 | Transport Layer Security 1.3 |
333 | ZGC: A Scalable Low-Latency Garbage Collector – Experimental |
335 | Deprecate the Nashorn JavaScript Engine |
336 | Deprecate the Pack200 Tools and API |
Erweiterung von var für Lambda Parameter
Das für Entwickler wohl größte Feature in JDK 10 war die Syntax für lokale Variablen und damit das teilweise Verzichten einer Typangabe auf der linken Seite. Mit JEP 323 wurde die Nutzung nun auf eine weitere Stelle, nämlich die Parameter von Lambda-Funktionen, erweitert.
Zwar konnte bei Lambda-Parametern bereits seit JDK 8 auf die Angabe
eines Typs verzichtet werden, dieser konnte dann jedoch nicht mehr mit einer
Annotation versehen werden. Annotationen für Parameter müssen nämlich immer am
Typ des Parameters stehen. Mit JDK 11 lässt sich ein solcher Fall nun mit dem
Wort var
(s. Listing 1) lösen.
Finale Version des neuen HTTP-APIs
Bereits in JDK 9 wurde im Rahmen von JEP 110 (HTTP/2 Client – Incubator) ein neues API für HTTP-Clients hinzugefügt. Die beiden primären Ziele waren dabei der Support von HTTP/2 und WebSockets sowie von blockierenden und nicht blockierenden Programmiermodellen.
Da die JDK Maintainer jedoch noch mehr Feedback für dieses API erhalten wollten, wurde es in JDK 9 lediglich als Inkubator-Modul aufgenommen und anschließend bereits im JDK 10 verbessert.
Mit JDK 11 sind die Maintainer nun der Meinung, dass die Programmierschnittstelle fertig ist, und sie integrieren dieses API mit dem JEP 321 als neues Modul. An der generellen Programmierschnittstelle hat sich dabei wenig geändert.
Listing 2 zeigt, wie das neue API verwendet wird, um ein HTTP GET
-Anfrage auf
die URI http://www.google.de
abzusenden. Der Body der Antwort wird
anschließend in einen String umgewandelt und auf der Konsole ausgegeben. Zudem
kommt, am Methodennamen sendAsync
zu erkennen, die asynchrone Variante der
Programmierschnittstelle zum Einsatz.
Ausführen von Einzeldatei-Java-Programmen
Ein weiteres neues Feature wurde mit JEP 330 umgesetzt. Es erlaubt das Ausführen
einer einzelnen Java-Quelldatei, ohne weitere Abhängigkeiten, ohne dass diese
vorher explizit kompiliert werden muss. Somit lässt sich eine Klasse wie in
Listing 3 direkt mit dem Befehl java Greet.java
ausführen.
In einer zweiten Variante wurde zudem noch Support für direkt ausführbare Dateien eingebaut. Für diese wird innerhalb der Datei als erste Zeile eine Shebang-Direktive eingefügt. Listing 4 erweitert das Beispiel aus Listing 3 um diese Direktive.
Diese Datei darf nun nicht mehr auf .java
enden, da sie streng genommen keine
reine Java-Quelldatei mehr ist. Wird nun dafür gesorgt, dass diese Datei
ausführbar ist, zum Beispiel mit chmod +x greet
unter Linux/MacOS, dann kann
diese direkt mit dem Befehl ./greet
ausgeführt werden und die Begrüßung
erscheint.
Dieses Feature erleichtert es somit, kleinere Skripte in Java zu schreiben. Die Limitierung auf eine einzelne Klasse und kleinere Fremdbibliotheken schränkt das Feature jedoch wieder ein.
JVM/Bytecode-Erweiterungen
Im Rahmen von Java 11 wurden drei JEPs umgesetzt, die sich primär um Erweiterungen der JVM selbst beziehungsweise des Bytecode-Formats gekümmert haben.
In JEP 181 ging es darum, direkten Support für Zugriffsregeln bei der Nutzung
von inneren Klassen zu implementieren. Angenommen, es existiert eine Klasse
Host
und innerhalb dieser ist wiederum eine Klasse Member
definiert. Die JVM
erlaubt bei diesem Konstrukt, dass die innere Klasse Member
auch auf private
Instanzvariablen von Host
zugreifen darf (s. Listing 5).
Wird diese Datei kompiliert, existieren anschließend zwei Class-Dateien, eine pro Klasse. Wird nun der vom Compiler erzeugte Bytecode betrachtet, lässt sich erkennen, dass der Compiler ein paar Dinge hinzufügt, damit diese Verschachtelung funktioniert (s. Listing 6).
Zum einen erzeugt der Compiler für die Klasse Host eine zusätzliche statische
Methode access$000
und zum zweiten erhält die Klasse Member
eine
Instanzvariable von Host
, welche über den Konstruktor gesetzt wird. Durch die
Kombination dieser beiden generierten Konstrukte ist nun der Zugriff auf die
Variable i
von Host
aus der Methode print
in Member
möglich.
Wenn die Kompilierung allerdings mit Java 11 durchgeführt wurde, ergibt sich
anderer Bytecode (s. Listing 7). Es lässt sich erkennen, dass nun keine
statische Methode access$000
mehr vom Compiler erzeugt wird.
Diese sogenannte Bridge-Methode war bis Java 11 notwendig, um die Zugriffsregeln von Java nicht zu verletzen. Immerhin versucht hier eine Klasse, direkt auf eine private Instanzvariable einer anderen Klasse zuzugreifen. Dank JEP 181 ist dieser Trick des Compilers nun nicht mehr notwendig. Im Bytecode werden dazu die Beziehungen von verschachtelten Klassen festgehalten. Die Zugriffsregelungen werden anschließend mit diesen Informationen geprüft. Diese Änderung hat auch Auswirkungen auf reflexive Zugriffe (s. Listing 8).
Mit Java 10 oder früher wird hier eine java.lang.IllegalAccessException
geworfen, obwohl das Feld laut Zugriffsregelungen für die Klasse Member
sichtbar ist. Es musste somit noch field.setAccessible(true)
aufgerufen
werden. Mit Java 11 funktioniert es nun wie erwartet.
Weiterhin wurde mit JEP 309 ein eine neue Bytecode-Instruktion
CONSTANT_Dynamic
eingeführt, und JEP 315 optimiert intrinsische Funktionen für
Strings
, Arrays
und einige Funktionen aus java.lang.Math
auf ARM CPUs.
Neue experimentelle Garbage-Collectoren
Die Aufgabe eines Garbage-Collectors (GC) besteht darin, Objekte auf dem Heap zu allokieren und nicht mehr benötigte Objekte vom Heap zu entfernen, um wieder Platz für weitere Objekte zu schaffen. Java 11 bringt hierzu insgesamt zwei neue, experimentelle GCs mit.
Der Epsilon GC (JEP 318) kann lediglich neue Objekte allokieren. Ein Aufräumen
des Heaps ist nicht vorgesehen. Demnach wird eine Anwendung, die mit ihm
gestartet wurde, mit einer OutOfMemoryException
enden, sobald der für die JVM
zur Verfügung stehende Speicher einmal komplett aufgebraucht wurde. Es wird kein
Versuch unternommen, nicht mehr erreichbare Objekte freizugeben und damit wieder
Speicher zur Verfügung zu stellen.
Auf den ersten Blick erscheint ein GC, der keinen Speicher freiräumt, als eher nutzlos. Auf den zweiten gibt es dann doch ein paar Anwendungsfälle, in denen ein solcher GC hilft:
- Er kann dazu verwendet werden, eine Base-Line zu erzeugen und diese mit anderen GCs zu vergleichen. Somit lässt sich ermitteln, wie viel Overhead ein „richtiger“ GC benötigt.
- Das Testen von Code unter Speicherdruck wird einfacher.
- Sehr kurze Jobs, die nie in Gefahr geraten, eine GC zu benötigen, werden beschleunigt.
- Er kann als letztes Mittel zum Optimieren von Applikationen verwendet werden, bei denen genau bekannt ist, wie viel Speicher benötigt wird, oder bei denen ein Neustart performanter als eine GC ist.
Um den Epsilon GC verwenden zu können, muss beim Start der JVM der Schalter
-XX:+UseEpsilonGC
angegeben werden. Da es sich um ein experimentelles Feature
handelt, muss zudem -XX:+UnlockExperimentalVMOptions
angegeben werden.
Als zweiter GC wurde in JEP 333 noch der ZGC umgesetzt. Das primäre Ziel dieses GCs ist es, dass keine der durch den GC verursachten Pausen länger als zehn Millisekunden dauert. Nebenbei soll der Durchsatz im Verhältnis zum aktuellen Standard GC G1 um nicht mehr als 15 Prozent sinken.
Wie auch beim Epsilon GC muss der ZGC beim Starten der JVM mit -XX:UseZGC
explizit angeschaltet werden. Zudem ist dieser aktuell nur auf Linux 64bit
vorhanden.
Sicherheit
Auch Themen rund um den Bereich Sicherheit haben es ins JDK 11 geschafft. MitJEP 332 wurde der TLS-Standard in Version 1.3 implementiert.
Zudem wurden mit den JEPs 324 und 329 die zwei neuen elliptischen Kurven Curve25519 und Curve448 sowie die beiden Algorithmen ChaCha20 und Poly1305 hinzugefügt.
Aufräumarbeiten
Lange Zeit war es so, dass im JDK immer nur Dinge hinzugefügt, aber nicht entfernt wurden. Dies ändert sich nun langsam, aber sicher. Im JDK 11 entfällt ein komplettes API, zwei weitere wurden als Deprecated markiert und werden voraussichtlich in Zukunft vollständig entfernt.
JEP 320 entfernt das Java EE und CORBA API aus dem JDK. Auf den ersten Blick erscheint dies für viele Anwendungen kein Problem zu sein, setzen doch viele neue Anwendungen auf HTTP oder Messaging.
Ein Teil des Java EE APIs ist jedoch auch JAXB. Setzt eine Anwendung also auf
diesen Standard zur Verarbeitung von XML-Dokumenten, muss bei der Migration auf
JDK 11 etwas Arbeit eingeplant werden. Glücklicherweise ist es hierbei in der
Regel damit getan, eine JAXB-Implementierung als externe Abhängigkeit
hinzuzufügen. Dies hat zwar den Nachteil einer weiteren externen Abhängigkeit,
ermöglicht aber auch einen einfacheren Einsatz von neueren Versionen. Wer dies
einmal benötigte, Stichwort lib/endorsed
, weiß den neuen Weg zu schätzen.
Weiterhin wurden mit JDK 11 Nashorn (JEP 335) und Pack200 (JEP 336) als Deprecated markiert. Zudem sind diese auch für eine Entfernung in einem späteren JDK vorgesehen. Wird also Nashorn zur Ausführung von JavaScript oder Pack200 zur Komprimierung verwendet, entsteht in Zukunft vermutlich Migrationsaufwand.
Sollten sich jedoch Maintainer für diese Features finden, kann es auch passieren, dass diese weiter im JDK vorhanden sein werden. Alternativ werden diese beiden Features als eigenständige Open-Source-Projekte weitergeführt. Idealerweise reicht anschließend eine Einbindung als externe Abhängigkeit, wie auch mit JAXB.
Einsichten
Die JVM bietet schon lange diverse Möglichkeiten, um während der Laufzeit an relevante Metriken und Informationen von ihr zu gelangen. JDK 11 erweitert diese durch die beiden JEPS 328 und 331.
JEP 328 erweitert das bereits mit JEP 167 (Event-Based JVM Tracing) bestehende Framework, um während der Laufzeit einer JVM Trace-Events zu produzieren.
Die Funktionalität existierte dabei schon länger innerhalb des OracleJDKs, war jedoch nur zahlenden Kunden vorbehalten. Im Zuge der Angleichung von Open- und OracleJDK hat Oracle dieses Feature Open Source gestellt und dem OpenJDK übergeben.
Das JDK wird somit um die folgenden Trace-Event-Features erweitert:
- Ein Java-API, um aus Java heraus Events erzeugen und lesen zu können.
- Einen Puffer-Mechanismus und ein binäres Datenformat.
- Die Möglichkeit, zur Laufzeit nur bestimmte Events zu erzeugen beziehungsweise diese generell zu filtern.
Zudem werden nun an diversen Stellen innerhalb des JDKs neue Events erzeugt, die eine detailliertere Überwachung erlauben.
Gleichzeitig ermöglicht JEP 331 ein verbessertes Profiling des Heaps der JVM. Somit ist es nun für Tools wie VisualVM, Java Flight Recorder oder YourKit einfacher und performanter, mitzubekommen, wann Heap für ein Objekte allokiert oder wann dieser wieder freigegeben wird.
Unicode-Upgrade
Teil von fast jedem JDK-Update ist ein Update des Unicode-Standards. Basierte JDK 10 noch auf Unicode 8.0, wird mit dem JDK nun Unicode 10.0 unterstützt. Somit können nun, dank JEP 327, über 16.000 weitere Unicode-Zeichen verwendet werden.
Erweiterungen von existierenden Klassen/Interfaces
Neben den größeren Features wurden auch im JDK 11 wieder bereits bestehende Klassen und Interfaces erweitert.
Im java.io
-Paket haben die beiden Klassen FileReader
und FileWriter
nun
einen weiteren Konstruktor, über den sich das Encoding der Datei angeben lässt.
Zudem gibt es nun statische Methoden, um Input- und Outputstreams sowie Reader
und Writer zu erzeugen, die nichts lesen oder sämtlichen Output verwerfen.
java.lang.String
wurde direkt um sechs neue Methoden ergänzt. Die drei
Methoden strip()
, stripLeading()
und stripTrailing()
können genutzt
werden, um Whitespace am Anfang, Ende oder an beiden Stellen zu entfernen. Der
Unterschied von strip()
zu trim()
besteht darin, dass strip()
alle Zeichen
entfernt für die Character::isWhitespace()
true
ergibt. trim()
entfernt
hingegen alle Zeichen, deren Zahlenwert kleiner oder gleich 20 ist.
Zusätzlich wurde String
noch um die Methode isBlank()
ergänzt, die den
Einsatz des Musters trim().isEmpty()
ablöst. Vorher muss allerdings bei Bedarf
weiterhin geprüft werden, ob der zu testende String
ungleich null
ist.
Die beiden Methoden repeat(int)
, um einen gegebenen String
x-mal zu
wiederholen, und lines()
, um einen String
in einen Stream
mit allen Zeilen
zu konvertieren, vervollständigen die Erweiterungen von String
.
In java.util.Optional
wurde nach langer Diskussion die Methode isEmpty()
ergänzt, und java.util.function.Predicate
wurde um eine statische Methode
not
erweitert, welche das als Parameter übergebene Predicate
negiert.
Weiterhin gibt es in java.util.regex.Pattern
mit der Methode
asMatchPredicate()
eine direkte Möglichkeit, ein Pattern
als Filter
für
Streams zu verwenden.
Zwei besondere Änderungen finden sich in der Klasse java.lang.Thread
. Hier
wurden die Methoden stop(Throwable)
und destroy()
entfernt. Dies ist
insofern besonders, da historisch im JDK nur äußerst selten Methoden wirklich
entfernt wurden. Da beide Methoden jedoch bereits seit einiger Zeit lediglich
eine Exception geworfen haben, sollte diese Änderung wenig alten Code brechen.
Ein vollständiger Report über alle Änderungen lässt sich mit dem JDK-API Diff Report Generator erzeugen. Einen veröffentlichten Report für JDK 11 gibt es bereits fertig unter https://gunnarmorling.github.io/jdk-api-diff/jdk10-jdk11-api-diff.html.
Support-Modell
Vor dem Release von JDK 11 kamen diverse Diskussionen auf, wie die Zukunft von Support und Builds wohl aussehen würde. Jetzt, nach dem Release, gibt es ein klares Bild.
Oracle stellt insgesamt zwei Builds des OpenJDKs zur Verfügung:
- Das Oracle JDK steht dabei unter einer Oracle spezifischen Lizenz und darf in Produktion nicht ohne bezahlten Support eingesetzt werden.
- Der zweite Build ist der offizielle OpenJDK Build. Dieser steht unter der freien Lizenz GPL2 mit Classpath-Erweiterung zur Verfügung.
Diese Builds werden allerdings nur sechs Monate zur Verfügung gestellt und auch der Support endet mit Erscheinen des nächsten JDK-Releases.
Soll ein von Oracle gebautes und unterstütztes JDK eingesetzt werden, müssen demnach Lizenzkosten gezahlt werden oder es muss alle sechs Monate ein Upgrade des JDKs auf die aktuellste Version durchgeführt werden.
Als Alternative hierzu gibt es die AdoptOpenJDK Community. Diese stellt, durch Sponsoren finanziert, eigene Builds des OpenJDKs zur Verfügung. Dabei ist geplant, dass auch nach dem Erscheinen von JDK 12, bis mindestens September 2022, Builds von JDK 11 zur Verfügung zu stellen. Somit kann dieses JDK auch ohne Lizenzkosten für einen langen Zeitraum eingesetzt werden.
Natürlich gibt es auch weiterhin kommerzielle JDK Builds von Firmen, wie Azul Systems, Red Hat, SAP oder anderen. Auch diese können, wie das Oracle JDK, in der Regel nur mit Support, und somit bezahlt, in Produktion eingesetzt werden.
Fazit
Im Vorfeld von JDK 11 hat vor allem die Diskussion um den Support dominiert. Mit der AdoptOpenJDK Community hat sich hier eine neue Partei ins Spiel gebracht, die auch weiterhin freie und somit kostenlose Builds zur Verfügung stellt. Alternativ kann bei diversen Herstellern auch kommerzieller Support erworben werden. Abseits von dieser Thematik bringt JDK 11 17 neue JEPs und viele weitere kleine API-Änderungen mit.
Da es sich beim JDK 11 um ein LTS-Release handelt, bietet sich ein direktes Upgrade auf dieses an. Wer noch auf JDK 8 geblieben ist, sollte sich überlegen, direkt auf 11 zu gehen, da der Aufwand quasi identisch zu einem Upgrade von 8 auf 9 ist. Ein guter Guide für eine Migration von 8 auf 11 bietet der englische Blog Post „Migrate Maven Projects to Java 11“ von Benjamin Winterberg.
Der Leitsatz „Nach dem JDK ist vor dem JDK“ gilt vor allem durch den verkürzten Releasezyklus auf sechs Monate. So ist bereits für den 19.03.2019 JDK 12 geplant. Aus Entwicklersicht sind dabei bereits jetzt die beiden Preview Features Switch Expressions (JEP 325) und Raw String Literals (JEP 326) interessant und bereits vorab einen Blick Wert. Listing 9 zeigt die aktuell geplante Syntax für beide Features.
Auch in Zukunft bleibt die Entwicklung von Java und dem JDK spannend. Die Reduzierung der Zeit zwischen Releases auf sechs Monate ermöglicht einen kontinuierlichen Zufluss von kleinen und mittleren Features. Somit besteht die Möglichkeit, schrittweise Neuerungen einzuführen, von denen die gesamte Community profitiert. Weiterhin besteht für große Codebasen die Möglichkeit, von LTS- zu LTS-Release zu migrieren und somit nicht alle sechs, sondern nur alle drei Jahre einen Versionssprung vollziehen zu müssen.