Java 14 Features mit Beispielen

Java 14 Features (mit Beispielen)

von Sven Woltmann – 15. November 2021

Artikelserie: Java-Versionen

Teil 1: Neue Features in Java 10

Teil 2: Neue Features in Java 11

Teil 3: Neue Features in Java 12

Teil 4: Neue Features in Java 13

Teil 5: Neue Features in Java 14

Teil 6: Neue Features in Java 15

(Melde dich für den HappyCoders-Newsletter an,
um sofort über neue Teile informiert zu werden.)

Bonus:

Java-Version unter Windows umschalten

Nachdem das vorherige Release eher klein ausgefallen war, wurde am 17. März 2020 Java 14 mit eindrucksvollen 16 umgesetzten JDK Enhancement Proposals (JEPs) veröffentlicht.

Mit Java 14 hält es auch wieder eine große Änderung Einzug in die Sprache: Switch Expressions.

Ein weiteres nützliches Feature, "Helpful NullPointerExceptions", wird uns in Zukunft viel Arbeit bei der Fehlersuche abnehmen.

Mit Records und "Pattern Matching for instanceof" sind außerdem zwei spannende Previews mit von der Partie.

Wie immer gibt es auch einige Performance-Verbesserungen; und eine ganze Menge Features wurde als "deprecated" markiert oder entfernt.

Switch Expressions (Standard)

Mit Switch Expressions erreicht die zweite Sprachverbesserung aus Project Amber den Produktionsstatus (die erste war "var" in Java 10). Bei Switch Expressions handelt es sich eigentlich um zwei Erweiterungen, die unabhängig voneinander, aber auch kombiniert, eingesetzt werden können:

  1. Pfeilnotation ohne break und fall-throughs
  2. Verwendung von switch als Ausdruck mit Rückgabewert

Schauen wir uns die Änderungen eine nach der anderen an (ich verwende dazu die Beispiele aus dem JEP, leicht abgewandelt).

Ausgangslage

Im folgenden Beispiel wird für einen Wochentag die Wortlänge ausgegeben. Für die letzten beiden Fälle habe ich den Code etwas komplizierter als nötig geschrieben, um im Folgenden zu demonstrieren, was mit Switch Expressions möglich ist.

switch (day) { case MONDAY: case FRIDAY: case SUNDAY: System.out.println(6); break; case TUESDAY: System.out.println(7); break; case THURSDAY: case SATURDAY: System.out.println((int) Math.pow(2, 3)); break; case WEDNESDAY: int three = 1 + 2; System.out.println(three * three); break; }
Code-Sprache: Java (java)

Diese Schreibweise ist zum einen unübersichtlich, zum anderen fehleranfällig aufgrund der sogenannten "Fall-Throughs", also der Ausführungsfortsetzung beim nächsten Fall, wenn der vorherige nicht mit einem break-Statement abgeschlossen wurde.

Entsprechend werden solche Konstrukte auch von den gängigen Tools für statische Code-Analyse (SCA) wie Sonar, Checkstyle und PMD als Code Smells moniert:

Warnungen der statischen Code-Analyse (SCA) bei Switch-Statements ohne break
Warnungen der statischen Code-Analyse (SCA) bei Switch-Statements ohne break

Pfeilnotation

Ab Java 14 kannst du statt des Doppelpunkts einen Pfeil verwenden. Vor dem Pfeil darfst du mehrere Fälle Komma-separiert auflisten. Nach dem Pfeil darf dann entweder ein einzelnes Code-Statement folgen oder ein Code-Block in geschweiften Klammern. Die break Statements fallen in dieser Notation weg.

switch (day) { case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); case TUESDAY -> System.out.println(7); case THURSDAY, SATURDAY -> System.out.println((int) Math.pow(2, 3)); case WEDNESDAY -> { int three = 1 + 2; System.out.println(three * three); } }
Code-Sprache: Java (java)

Moderne IDEs wie IntelliJ erkennen das Verbesserungspotenzial und bieten eine automatische Konvertierung in das neue Format an:

Automatische Ersetzung von 'switch' Statements
Automatische Ersetzung von 'switch' Statements

Switch als Ausdruck

Oft verwenden wir switch, um einer Variablen einen Fall-spezifischen Wert zuzuweisen. Im folgenden Beispiel geben wir die Länge des Wochentages nicht aus, sondern speichern sie in der Variablen numLetters:

int numLetters; switch (day) { case MONDAY: case FRIDAY: case SUNDAY: numLetters = 6; break; case TUESDAY: numLetters = 7; break; case THURSDAY: case SATURDAY: numLetters = (int) Math.pow(2, 3); break; case WEDNESDAY: int three = 1 + 2; numLetters = three * three; break; default: throw new IllegalStateException("Unknown day: " + day); }
Code-Sprache: Java (java)

Damit wir die Variable im Anschluss verwenden können, müssen wir – obwohl wir jeden Wochentag abgedeckt haben – entweder die Variable vorab initialisieren oder einen default-Case angeben. Andernfalls würde der Compiler mit der Fehlermeldung "Variable 'numLetters' might not have been initialized" abbrechen.

Ab Java 14 können wir dieses Statement in einen Ausdruck umwandeln. Dabei geben wir mit dem neuen Keyword yield jeweils einen Wert zurück. Das Ergebnis des Switch-Ausdrucks weisen wir dann direkt der Variablen zu:

int numLetters = switch (day) { case MONDAY: case FRIDAY: case SUNDAY: yield 6; case TUESDAY: yield 7; case THURSDAY: case SATURDAY: yield (int) Math.pow(2, 3); case WEDNESDAY: int three = 1 + 2; yield three * three; default: throw new IllegalStateException("Unknown day: " + day); };
Code-Sprache: Java (java)

Falls du yield in deinem Quellcode als Variablenname verwendet haben solltest – keine Sorge, das kannst du weiterhin tun. Selbst so etwas wäre erlaubt:

int yield = 5; yield yield + yield;

Kombination von Pfeilnotation und Switch-Ausdruck

Deutlich eleganter wird der eben gezeigte Switch-Ausdruch, wenn wir ihn in Pfeilnotation schreiben. Dabei können wir die jeweiligen Werte direkt hinter den Pfeil schreiben. Oder – im Beispiel bei WEDNESDAY – aus einem Code-Block mit yield zurückgeben:

int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> (int) Math.pow(2, 3); case WEDNESDAY -> { int three = 1 + 2; yield three * three; } default -> throw new IllegalStateException("Unknown day: " + day); };
Code-Sprache: Java (java)

Das komplette Refactoring vom herkömmlichen Switch Statement zur Switch Expression mit Pfeilnotation können wir auch unsere IDE erledigen lassen:

 Automatische Ersetzung von 'switch' Statements durch 'switch' Expressions
Automatische Ersetzung von 'switch' Statements durch 'switch' Expressions

Da es sich bei der Variablen day um ein Enum handelt, kann der Compiler erkennen, dass wir alle Fälle abgedeckt haben. Somit darf der default-Case wegfallen:

int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> (int) Math.pow(2, 3); case WEDNESDAY -> { int three = 1 + 2; yield three * three; } };
Code-Sprache: Java (java)

Auch das erledigt unsere IDE gerne für uns:

Automatische Entfernung des 'default'-Branches
Automatische Entfernung des 'default' Branches

Die Schreibweise ohne default-Fall ist nicht nur kürzer, sondern hilft uns auch bei zukünftigen Erweiterungen des Enums. Sollten wir dieses – z. B. um einen NEWDAY – erweitern, wird der Compiler uns mitteilen, dass der Switch-Ausdruck nun unvollständig ist:

Unvollständige 'switch' Expression
Unvollständige 'switch' Expression

Damit hast du alle neuen switch-Möglichkeiten, die Java 14 bietet, kennengelernt. Ich wünsche dir, dass du bei deiner nächsten Aufgabe die Möglichkeit hast ein aktuelles JDK zu benutzen, um die erweiterten Switch Statements und Expressions einsetzen zu können.

(Erstmals wurden Switch Expressions als Preview-Feature in Java 12 vorgestellt. Im zweiten Preview in Java 13 wurde das ursprünglich zur Rückgabe von Werten verwendete Keyword break durch yield ersetzt. Aufgrund des positiven Feedbacks wurden Switch Expressions in Java 14 mit dem JDK Enhancement Proposal 361 ohne weitere Änderungen als finales Feature veröffentlicht.)

Helpful NullPointerExceptions

Wir kennen alle das folgende Problem: Unser Code wirft eine NullPointerException:

Exception in thread "main" java.lang.NullPointerException at eu.happycoders.BusinessLogic.calculate(BusinessLogic.java:80)
Code-Sprache: Klartext (plaintext)

Und im Code finden wir dann so etwas:

long value = context.getService().getContainer().getMap().getValue();
Code-Sprache: Java (java)

Was ist jetzt null?

  • context?
  • context.getService()?
  • Service.getContainer()?
  • Container.getMap()?
  • Map.getValue()? (falls diese Methode ein Long-Objekt zurückgibt)

Um den Fehler zu beheben, haben wir folgende Möglichkeiten:

  • Wir könnten den Quellcode analysieren.
  • Wir könnten die Applikation debuggen (sehr aufwändig, wenn der Fehler schwer reproduzierbar ist oder nur in Produktion auftritt).
  • Wir könnten den Code auf mehrere Zeilen aufteilen und erneut ausführen (wir müssten die Applikation neu deployen und abwarten, bis der Fehler erneut auftritt).

Nach einem Upgrade auf Java 14 wirst du dir diese Frage nicht mehr stellen müssen, denn dann sieht die Fehlermeldung z. B. so aus:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Map.getValue()" because the return value of "Container.getMap()" is null at eu.happycoders.BusinessLogic.calculate(BusinessLogic.java:80)
Code-Sprache: Klartext (plaintext)

Wir können nun ganz genau ablesen, an welcher Stelle die Exception aufgetreten ist: Container.getMap() lieferte null zurück, so dass Map.getValue() nicht aufgerufen werden konnte.

Ähnliches kann beim Zugriff auf Arrays passieren, wie z. B. in folgender Code-Zeile:

this.world[x][y][z] = value;
Code-Sprache: Java (java)

Tritt in dieser Zeile eine NullPointerException auf, war bisher nicht erkennbar, ob world, world[x] oder world[x][y] null war. Mit Java 14 geht das klar aus der Fehlermeldung hervor:

Exception in thread "main" java.lang.NullPointerException: Cannot store to int array because "this.world[x][y]" is null at eu.happycoders.BusinessLogic.calculate(BusinessLogic.java:107)
Code-Sprache: Klartext (plaintext)

In Java 14 sind Helpful NullPointerExceptions noch standardmäßig deaktiviert und müssen mit -XX:+ShowCodeDetailsInExceptionMessages aktiviert werden. In Java 15 wird das Feature dann von Haus aus aktiviert sein.

(Helpful NullPointerExceptions sind in JDK Enhancement Proposal 358 spezifiziert.)

Experimentelle, Preview- und Incubator-Features

Die folgenden Features sind noch nicht produktionsreif. Sie werden in einem mehr oder weniger fortgeschrittenen Entwicklungsstadium mit ausgeliefert, um sie ggf. anhand von Feedback aus der Java-Community noch verbessern zu können.

Die ersten drei Features (Records, Pattern Matching for instanceof und Text Blocks) werden (wie Switch Expressions) in Project Amber entwickelt, dessen Ziel es ist, die Java-Syntax moderner und prägnanter zu machen.

Ich werde die Features nicht in allen Einzelheiten vorstellen, sondern jeweils nur kurz anreißen und auf die Java-Version verweisen, in denen die Features Produktionsreife erlangen. Im entsprechenden Artikel dieser Serie werden sie dann ausführlich behandelt.

Records (Preview)

Das erste neue Preview-Feature in Java 14 sind Records, definiert in JDK Enhancement Proposal 359.

Ein Record bietet eine kompakte Syntax für eine Klasse mit nur finalen Feldern. Diese werden im Konstruktor gesetzt und sind über Zugriffsmethoden auslesbar.

Hier ein einfaches Beispiel:

record Point(int x, int y) {}
Code-Sprache: Java (java)

Dieser Einzeiler erzeugt eine Klasse Point mit

  • finalen Instanzfeldern x und y,
  • einem Konstruktor, der beide Felder setzt
  • und Zugriffsmethoden x() und y() zum Lesen der Felder.

Point kann wie folgt eingesetzt werden:

Point p = new Point(3, 5); int x = p.x(); int y = p.y();
Code-Sprache: Java (java)

Die Methoden equals(), hashCode() und toString() werden für Records automatisch generiert.

Records können um statische Felder und Methoden erweitert werden, nicht aber um Instanzfelder. Sie können Interfaces implementieren, aber nicht von anderen Klassen erben. Sie sind implizit final, man kann also auch nicht von ihnen erben.

Eine detaillierte Vorstellung erfolgt im Artikel über Java 16, wenn Records Produktionsreife erreicht haben.

Pattern Matching for instanceof (Preview)

Das zweite Preview in Java 14 ist "Pattern Matching for instanceof". Dieses Feature, definiert in JDK Enhancement Proposal 305, eliminiert die lästige Notwendigkeit nach einer instanceof-Prüfung einen Cast auf den Zieltyp durchzuführen.

Am einfachsten lässt sich das an einem Beispiel zeigen.

Der folgende Code bekommt das Object obj geliefert. Wenn es ein String ist und länger als fünf Zeichen, soll es in Großbuchstaben umgewandelt und ausgegeben werden. Ist es hingegen ein Integer, soll es quadriert und ausgegeben werden.

Dazu müssen wir in den Zeilen 4 und 9 jeweils einen Cast durchführen.

Object obj = getObject(); if (obj instanceof String) { String s = (String) obj; if (s.length() > 5) { System.out.println(s.toUpperCase()); } } else if (obj instanceof Integer) { Integer i = (Integer) obj; System.out.println(i * i); }
Code-Sprache: Java (java)

Viele von uns haben sich so an diese Schreibweise gewöhnt, dass wir sie gar nicht mehr in Frage stellen. Doch es geht besser!

Ab Java 14 können wir die Casts weglassen und den Code stattdessen wie folgt schreiben:

if (obj instanceof String s) { if (s.length() > 5) { System.out.println(s.toUpperCase()); } } else if (obj instanceof Integer i) { System.out.println(i * i); }
Code-Sprache: Java (java)

Hinter einem instanceof-Statement können wir jetzt einen Variablennamen angeben. Wenn obj vom angegebenen Typ ist, wird es an den neuen Variablennamen gebunden; diese neue Variable ist dann vom angegebenen Zieltyp und im "then-Block" sichtbar.

Wir können sogar noch einen Schritt weitergehen und die if-Statements der Zeilen 1 und 2 kombinieren:

if (obj instanceof String s && s.length() > 5) { System.out.println(s.toUpperCase()); } else if (obj instanceof Integer i) { System.out.println(i * i); }
Code-Sprache: Java (java)

Somit haben wir neun Zeilen Code auf fünf Zeilen reduziert und gleichzeitig die Lesbarkeit deutlich erhöht.

Genau wie Records wird auch "Pattern Matching for instanceof" in Java 16 production-ready sein.

Text Blocks (Second Preview)

Text Blocks wurden in Java 13 als Preview eingeführt. Sie ermöglichen die Notation mehrzeiliger Strings wie in folgendem Beispiel:

String sql = """ SELECT id, firstName, lastName FROM Employee WHERE departmentId = "IT" ORDER BY lastName, firstName""";
Code-Sprache: Java (java)

JDK Enhancement Proposal 368 führt in Java 14 zwei neue Escape-Sequencen ein:

Backslash am Zeilenende

Ein Backslash ('\') am Zeilenende signalisiert, dass vor der nächsten Zeile kein Zeilenumbruch erfolgen soll – ähnlich wie in Linux-Shell-Kommandos. Hier ein Beispiel:

String sql = """ SELECT * FROM Employee \ WHERE departmentId = "IT" \ ORDER BY lastName, firstName"""; System.out.println("sql = " + sql);
Code-Sprache: Java (java)

Das Programm gibt folgendes aus:

sql = SELECT * FROM Employee WHERE departmentId = "IT" ORDER BY lastName, firstName
Code-Sprache: Klartext (plaintext)

Leerzeichen mit \s

Abschließende Leerzeichen werden normalerweise entfernt, wie in folgendem Beispiel (die Punkte sollen Leerzeichen darstellen):

String text = """ one····· two····· three···"""; text.lines().map(line -> "|" + line + "|").forEachOrdered(System.out::println);
Code-Sprache: Java (java)

Folgendes gibt das Programm aus:

|one| |two| |three|
Code-Sprache: Klartext (plaintext)

In diesem Beispiel würden wir die Leerzeichen aber gerne erhalten. Ab Java 14 ist das möglich, indem wir statt Leerzeichen die Escape-Sequenz \s verwenden:

String text = """ one\s\s\s\s\s two\s\s\s\s\s three\s\s\s""";
Code-Sprache: Java (java)

Übersichtlicher und völlig ausreichend ist es, wenn wir nur das letzte Leerzeichen escapen :-)

String text = """ one \s two \s three \s""";
Code-Sprache: Java (java)

Jetzt gibt das Programm wie gewünscht das folgende aus:

|one | |two | |three |
Code-Sprache: Klartext (plaintext)

Text Blocks werden in der nächsten Version, Java 15, den Produktionsstatus erreichen. Im nächsten Teil der Serie werde ich sie in allen Einzelheiten beschreiben.

ZGC on macOS + Windows (Experimental)

Der ZGC, ein von Oracle entwickelter Garbage Collector mit dem Ziel Pausezeiten von maximal 10 ms zu erreichen, wurde erstmals in Java 11 als experimentelles Feature für Linux vorgestellt.

Mit den JDK Enhancement Proposals JEP 364 und JEP 365 ist der Z Garbage Collector nun auch unter macOS und Windows verfügbar (weiterhin als experimentelles Feature).

Du kannst ZGC mit den JVM-Flags -XX:+UnlockExperimentalVMOptions -XX:+UseZGC aktivieren.

ZGC wird in Java 15 produktionsreif sein. Im entsprechenden Artikel werde ich ihn ausführlich vorstellen.

Packaging Tool (Incubator)

Basierend auf dem JDK Enhancement Proposal 343 wird das Tool jpackage entwickelt, mit dem ein plattformspezifischer Installer für eine Java-Anwendung erstellt werden kann, der wiederum die Anwendung und die dafür benötigte JRE installiert.

Plattformspezifisch bedeutet dabei, dass sich der Installer für Benutzer einer bestimmten Plattform vertraut anfühlt. Für Windows ist das z. B. eine msi- oder eine exe-Datei, die per Doppelklick gestartet wird. Für macOS eine pkg- oder dmg-Datei. Und für Linux eine deb- oder rpm-Datei.

Die Funktionalität soll sich an das Tool javapackager anlehnen, das seit JDK 8 mit ausgeliefert wurde, allerdings in Java 11 mitsamt JavaFX wieder entfernt wurde.

jpackage wird in Java 16 Produktsionsreife erlangen. Im entsprechenden Artikel dieser Serie werde ich zeigen, wie man das Tool einsetzt.

Foreign-Memory Access API (Incubator)

Mit dem JDK Enhancement Proposal 370 wird eine API eingeführt, die es Java-Programmen erlaubt, effizient und sicher auf Speicher außerhalb des Java-Heaps zuzugreifen.

Die Foreign-Memory Access API ist ein Teil von Projekt Panama, das einen schnelleren und einfacher zu verwendenden Ersatz für das Java Native Interface (JNI) schaffen soll.

Diese Schnittstelle wird bis mindestens Java 17 im Incubator-Status bleiben.

Performance-Verbesserungen

In Java 14 gibt es einige Performance-Verbesserungen beim Zugriff auf Speicher und in den Garbage Collectoren.

Non-Volatile Mapped Byte Buffers

Mit einem MappedByteBuffer kannst du eine Datei in eine Speicherregion "mappen", um sie dann über reguläre Speicherzugriffe lesen und beschreiben zu können. Mehr dazu findest du im Abschnitt "Memory-mapped Files" der Artikelserie "Dateien in Java".

Geänderte Daten müssen regelmäßig in das Speichermedium übertragen werden. Für NVMs gibt es hierfür effizientere Vorgehensweisen mit weniger Overhead als bei herkömmlichen Speichermedien.

Ab Java 14 kannst du diese effizienten Mechanismen einsetzen. Dazu musst du beim Erstellen eines MappedByteBuffer angeben, dass die Datei auf einem NVM-Medium liegt. Dazu gibst du beim Aufruf von FileChannel.map() einen der neuen Modi ExtendedMapMode.READ_ONLY_SYNC bzw. ExtendedMapMode.READ_WRITE_SYNC an, wie in folgendem Beispiel:

try (FileChannel channel = FileChannel.open( Path.of("test-file.bin"), StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.READ)) { MappedByteBuffer buffer = channel.map(ExtendedMapMode.READ_WRITE_SYNC, 0, 256); // read from / write to the buffer }
Code-Sprache: Java (java)

Non-Volatile Mapped Byte Buffers sind nur unter Linux verfügbar, da nur dort die erforderlichen speziellen Betriebssystem-Aufrufe existieren.

(Non-Volatile Byte Buffers sind in JDK Enhancement Proposal 352 spezifiziert.)

NUMA-Aware Memory Allocation for G1

Auf modernen Maschinen mit mehreren CPUs und mehreren Kernen pro CPU spielt die physische Distanz zwischen CPU-Kern und Speichermodul eine immer größere Rolle. Je weiter ein Speichermodul vom CPU-Kern entfernt ist, desto höher die Latenz beim Speicherzugriff.

Durch den JDK Enhancement Proposal 345 kann der G1 Garbage Collector ab Java 14 solche Architekturen vorteilhaft nutzen, um die Gesamtperformance zu erhöhen.

Threads werden naheliegenden NUMA-Knoten zugeordnet. Objekte, die von einem Thread erstellt werden, werden auf immer demselben NUMA-Knoten angelegt. Und solange sie sich in der Young Generation befinden, verbleiben sie auf diesem, indem sie nur in Survivor Regions auf demselben NUMA-Knoten evakuiert werden.

NUMA-Aware Memory Allocation ist nur für Linux verfügbar und muss über die VM-Option +XX:+UseNUMA explizit aktiviert werden.

Parallel GC Verbesserungen

Im parallelen Garbage Collector wurde das Management für die parallele Abarbeitung von Tasks optimiert, was zu einer deutlichen Leistungsverbesserungen führen kann.

Deprecations und Löschungen

Die folgenden Funktionen wurden in Java 14 als "deprecated" oder "for removal" markiert oder endgültig aus dem JDK entfernt.

Thread Suspend/Resume Are Deprecated for Removal

Neben Thread.stop() sind auch die folgenden Methoden bereits seit Java 1.2 als "deprecated" markiert:

  • Thread.suspend()
  • Thread.resume()
  • ThreadGroup.suspend()
  • ThreadGroup.resume()
  • ThreadGroup.allowThreadSuspension()

Der Grund dafür ist, dass Thread-Pausierung äußerst anfällig ist für Deadlocks:

Wenn Thread.suspend() innerhalb eines synchronized-Blocks aufgerufen wird, bleibt der entsprechende Monitor mindestens solange gesperrt, bis Thread.resume() aufgerufen wird. Geschieht dies allerdings in einem anderen Thread, innerhalb eines synchronized-Blocks auf demselben Monitor, blockiert dieser zweite Thread beim Versuch den synchronized-Block zu betreten.

In Java 14 wurden die o. g. Methoden als "for removal" markiert.

Die ebenfalls als "deprecated" markierte – und in meinen Augen viel gefährlichere – Methode Thread.stop() wurde bisher nicht als "for removal" markiert und wird uns wohl noch eine Weile begleiten.

(Für diese Änderung existiert kein JDK Enhancement Proposal.)

Deprecate the Solaris and SPARC Ports

Das Betriebssystem Solaris und die SPARC-Prozessorarchitektur sind nicht mehr zeitgemäß. Um Entwicklungsresourcen anderweitig einsetzen zu können, hat Oracle im JDK Enhancement Proposal 362 vorgeschlagen, die Portierungen Solaris/SPARC, Solaris/x64 und Linux/SPARC in Java 14 als "deprecated" zu markieren und in einem der nächsten Releases komplett zu entfernen.

Remove the Concurrent Mark Sweep (CMS) Garbage Collector

Der Concurrent Mark Sweep (CMS) Garbage Collector wurde in Java 9 mit JEP 291 als "deprecated" markiert. Die Entwicklungsressourcen sollten zugunsten modernerer Garbage Collectoren wie G1GC und ZGC umverteilt werden.

Als Ersatz für CMS wird der bereits seit Java 6 verfügbare und in Java 9 zum Standard-Garbage-Collector erhobene Allrounder G1 empfohlen.

Mit JEP 363 wird in Java 14 CMS endgültig aus dem JDK entfernt.

Deprecate the ParallelScavenge + SerialOld GC Combination

Es existiert eine ungewöhnliche und selten genutzte Kombination von GC-Algorithmen: die Paarung aus parallelem GC-Algorithmus für die junge Generation ("ParallelScavenge") und seriellem Algorithmus für die alten Generation ("SerialOld").

Diese Kombination kann man aktivieren, indem man den ParallelGC aktiviert und gleichzeitig den ParallelOldGC deaktiviert, was wiederum automatisch den SerialOldGC aktiviert:

-XX:+UseParallelGC -XX:-UseParallelOldGC

So kann man eine Reduzierung des Gesamtspeicherverbrauch von bis zu 3 % des Java-Heaps erzielen.

Dieser Vorteil wiegt jedoch den hohen Wartungsaufwand nicht auf. Daher wurde entschieden, die Entwicklungsresourcen anderweitig einzusetzen und diese GC-Kombination in Java 14 durch JEP 366 als "deprecated" zu markieren.

Als Ersatz wird die Verwendung von parallelem GC sowohl für die junge als auch die alte Generation empfohlen, die wie folgt aktiviert wird:

-XX:+UseParallelGC

Bereits in Java 15 wird die Kombination ParallelScavenge + SerialOld nicht mehr verwendbar sein.

Remove the Pack200 Tools and API

Das in Java 5 eingeführte Kompressionsverfahren für .class- und .jar-Dateien, Pack200 wurde in Java 11 als "deprecated" markiert.

Der Aufwand um solch spezielle Kompressionsverfahren zu maintainen – nur um gegenüber Standardverfahren ein paar zusätzliche Bytes herauszukitzeln – steht in Zeiten von 100-MBit-DSL-Leitungen und 18-TB-Festplatten in keinem Verhältnis mehr zum Nutzen.

Mit dem JDK Enhancement Proposal 367 wurden Pack200 und die zugehörigen Tools jetzt endgültig aus dem JDK entfernt.

Sonstige Änderungen in Java 14

Habe ich in dieser Kategorie bisher immer Änderungen aufgelistet, die man als Java-Entwicklerin und -Entwickler nicht unbedingt kennen musste, sind in Java 14 auch die "Sonstigen Änderungen" durchaus interessant.

JFR Event Streaming

Im Artikel über Java 11 habe ich Java Flight Recorder (JFR) und JDK Mission Control (JMC) vorgestellt. Flight Recorder sammelt während der Ausführung einer Anwendung wertvolle Daten über die JVM und speichert sie in einer Datei. Die gespeicherten Daten können dann mit Mission Control visualisiert werden:

JDK Mission Control
JDK Mission Control

JDK Enhancement Proposal 349 ermöglicht ab Java 14 auch eine kontinuierliche Überwachung einer Java-Anwendung, indem die von Flight Recorder gesammelten Daten aus der laufenden Anwendung heraus ausgelesen werden können (anstatt sie in einer Datei zu speichern und im Nachhinein zu analyiseren).

Wie das funktioniert, zeigt folgender Beispiel-Quellcode:

int[] array = createRandomArray(1_000_000_000); try (var rs = new RecordingStream()) { rs.enable("jdk.CPULoad").withPeriod(Duration.ofSeconds(1)); rs.onEvent( "jdk.CPULoad", event -> { float jvmUser = event.getFloat("jvmUser"); float jvmSystem = event.getFloat("jvmSystem"); float machineTotal = event.getFloat("machineTotal"); System.out.printf( Locale.US, "JVM User: %5.1f %%, JVM System: %5.1f %%, Machine Total: %5.1f %%%n", jvmUser * 100, jvmSystem * 100, machineTotal * 100); }); rs.startAsync(); Arrays.parallelSort(array); }
Code-Sprache: Java (java)

Zunächst wird mit new RecordingStream() ein Stream von JFR-Events erzeugt.

Mit RecordingStream.enable() wird ein konkretes Event (im Beispiel "jdk.CPULoad" aktiviert).

Mit RecordingStream.onEvent() definieren wir, wie auf das Event reagiert werden soll. Das Event selbst besteht aus mehreren Datenfelder, die wir mit getFloat() oder – je nach Datentyp – anderen Gettern auslesen können.

Mit RecordingStream.startAsync() starten wir die Aufzeichnung in einem separaten Thread. Im Hauptthread sortieren wir ein Array mit einer Milliarde Elemente, was auf meinem Laptop etwa 15 Sekunden dauert.

Währenddessen sieht man anhand der Flight-Recorder-Daten gut, dass Arrays.parallelSort() die CPU nahezu vollständig auslastet:

JVM User: 45.1 %, JVM System: 15.0 %, Machine Total: 60.1 % JVM User: 86.5 %, JVM System: 0.5 %, Machine Total: 95.2 % JVM User: 91.8 %, JVM System: 0.3 %, Machine Total: 100.0 % JVM User: 93.0 %, JVM System: 0.2 %, Machine Total: 96.9 % ...
Code-Sprache: Klartext (plaintext)

Accounting Currency Format Support

In manchen Ländern, z. B. in den USA, werden negative Zahlen in der Buchhaltung nicht mit einem Minuszeichen gekennzeichnet, sondern durch runde Klammern.

In Java 14 wird die sogenannte Language-Tag Extension "u-cf-account" hinzugefügt, die es ermöglicht, in einem Locale-Objekt die Zusatzinformation anzugeben, ob dieses im Kontext der Buchhaltung verwendet wird.

Du kannst diese Extension wie in folgendem Beispiel einsetzen:

// Example *without* language tag extension Locale locale = Locale.forLanguageTag("en-US"); NumberFormat cf = NumberFormat.getCurrencyInstance(locale); System.out.println("Normal: " + cf.format(-14.95)); // Example *with* language tag extension Locale localeAccounting = Locale.forLanguageTag("en-US-u-cf-account"); NumberFormat cfAccounting = NumberFormat.getCurrencyInstance(localeAccounting); System.out.println("Accounting: " + cfAccounting.format(-14.95));
Code-Sprache: Java (java)

Ab Java 14 gibt das Programm folgendes aus:

Normal: -$14.95 Accounting: ($14.95)
Code-Sprache: Klartext (plaintext)

Wenn du das Programm unter Java 13 oder älter laufen lässt, wird die Tag Extension ignoriert, und das Programm formatiert beide Male gleich:

Normal: -$14.95 Accounting: -$14.95
Code-Sprache: Klartext (plaintext)

Weitere Unicode Language-Tag Extensions findest du im Artikel über Java 10.

Vollständige Liste aller Änderungen in Java 14

Dieser Artikel hat alle Features von Java 14 vorgestellt, die in JDK Enhancement Proposals definiert sind, sowie einige Performance-Verbesserungen und Löschungen, die keinem JEP zugeordnet sind.

Eine vollständige Liste aller Änderungen findest du in den offiziellen Java 14 Release Notes.

Fazit

Java 14 ist ein eindrucksvolles Release. Switch Expressions sind produktionsreif. Und dank "Helpful NullPointerExceptions" sparen wir uns in Zukunft eine Menge Debugging-Arbeit.

Mit Records und "Pattern Matching for instanceof" wurden zwei weitere Features aus Project Amber als Preview ins JDK übernommen. Text Blocks wurden um die Escape-Sequenzen "Backslash am Zeilenende" und "\s" erweitert.

Der (noch experimentelle) Low-Latency Garbage Collector ZGC ist nun auch unter Windows und macOS verfügbar. Und wer seit Java 11 den javapackager vermisst, kann ab Java 14 mit dessen Nachfolger jpackage experimentieren.

JFR Event Streaming und mehrere Performance-Verbesserungen runden das Release ab.

Wenn dir der Artikel gefallen hat, hinterlasse mir gerne einen Kommentar oder teile den Artikel über einen der Share-Buttons am Ende.

Und wenn du informiert werden möchtest, wenn der nächste Artikel online geht, dann klicke hier, um dich für den kostenlosen HappyCoders-Newsletter anzumelden.

Sven Woltmann
Über den Autor
Ich bin freiberuflicher Softwareentwickler mit über 20 Jahren Erfahrung in skalierbaren Java-Enterprise-Anwendungen. Mein Schwerpunkt liegt auf der Optimierung komplexer Algorithmen und auf fortgeschrittenen Themen wie Concurrency, dem Java Memory Model und Garbage Collection. Hier auf HappyCoders.eu möchte ich dir helfen, ein besserer Java-Programmierer zu werden. Lies mehr über mich hier.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Die folgenden Artikel könnten dir auch gefallen