Gründe für den Wechsel zu Java 11 und darüber hinaus

Die Frage ist nicht , ob Sie zu Java 11 oder einer späteren Version wechseln sollten, aber wann. Innerhalb der nächsten Jahre wird Java 8 nicht mehr unterstützt, und Benutzer müssen zu Java 11 oder höher wechseln. Die Migration zu Java 11 hat unserer Meinung nach gewisse Vorteile, und wir plädieren dafür, diese Migration baldmöglichst durchzuführen.

Seit Java 8 sind neue Features und Erweiterungen hinzugekommen. Es gibt beachtliche Ergänzungen und Änderungen für die API sowie Erweiterungen zur Verbesserung des Starts, der Leistung und der Speicherauslastung.

Migrieren zu Java 11

Die Migration zu Java 11 kann schrittweise erfolgen. Code muss keine Java-Module verwenden, um unter Java 11 ausgeführt werden zu können. Java 11 kann zum Ausführen von Code verwendet werden, der mit JDK 8 entwickelt und erstellt wurde. Es gibt jedoch einige potenzielle Probleme, die hauptsächlich mit veralteten APIs und Klassenladeprogrammen sowie mit der veralteten Reflektion zusammenhängen.

Die Microsoft Java Engineering Group hat eine Anleitung für die Umstellung von Java 8 auf Java 11 bereitgestellt. Weitere nützliche Leitfäden sind der Java-Plattform, Standard Edition, Oracle JDK 9-Migrationsleitfaden und Der Stand des Modulsystems: Kompatibilität und Migration.

Allgemeine Änderungen zwischen Java 8 und 11

Dieser Abschnitt enthält nicht alle Änderungen, die in Java-Versionen 9 [1], 10 [2] und 11 [3] vorgenommen wurden. Vielmehr werden Änderungen mit Auswirkungen auf die Leistung, Diagnose und Produktivität hervorgehoben.

Module [4]

Module dienen zur Behandlung von Problemen bei der Konfiguration und Kapselung, die in umfangreichen, am Klassenpfad ausgeführten Anwendungen schwierig zu bewältigen sind. Bei einem Modul handelt es sich um eine selbstbeschreibende Sammlung von Java-Klassen und -Schnittstellen sowie von zugehörigen Ressourcen.

Module ermöglichen die Anpassung von Laufzeitkonfigurationen, die nur die für eine Anwendung erforderlichen Komponenten enthalten. Diese Anpassung sorgt für einen geringeren Speicherbedarf und ermöglicht die statische Verknüpfung einer Anwendung (mithilfe von jlink) mit einer benutzerdefinierten Runtime für die Bereitstellung. Der geringere Speicherbedarf ist insbesondere in einer Microservices-Architektur von Vorteil.

Intern können Module von JVM auf eine Weise genutzt werden, die das Laden von Klassen effizienter macht. Das Ergebnis ist eine kleinere, kompaktere und schneller startende Runtime. Optimierungstechniken, die von JVM zur Verbesserung der Anwendungsleistung verwendet werden, können effektiver genutzt werden, da Module codieren, welche Komponenten eine Klasse benötigt.

Programmierer können mithilfe von Modulen eine starke Kapselung erreichen, indem sie eine explizite Deklaration der von einem Modul exportierten Pakete und der erforderlichen Komponenten erzwingen und den reflektierenden Zugriff beschränken. Dieses Maß an Kapselung trägt zur Verbesserung der Sicherheit und Verwaltbarkeit von Anwendungen bei.

Eine Anwendung kann weiterhin den Klassenpfad verwenden und muss nicht auf Module umgestellt werden, um unter Java 11 ausgeführt werden zu können.

Profilerstellung und Diagnose

Java Flight Recorder [5]

Java Flight Recorder (JFR) sammelt Diagnose- und Profilerstellungsdaten einer aktiven Java-Anwendung. JFR hat nur minimale Auswirkungen auf die aktive Java-Anwendung. Die gesammelten Daten können dann mit Java Mission Control (JMC) und anderen Tools analysiert werden. In Java 8 waren JFR und JMC kommerzielle Features. In Java 11 stehen sie dagegen als Open-Source-Features zur Verfügung.

Java Mission Control [6]

Java Mission Control (JMC) bietet eine grafische Anzeige von Daten, die vom Java Flight Recorder (JFR) gesammelt werden und Open Source in Java 11. Zusätzlich zu allgemeinen Informationen zur ausgeführten Anwendung ermöglicht JMC dem Benutzer, einen Drilldown in die Daten durchzuführen. JFR und JMC können zur Diagnose von Runtimeproblemen wie Speicherverlusten, GC-Overhead, „heißen“ Methoden, Threadengpässen und blockierenden Eingaben/Ausgaben verwendet werden.

Einheitliche Protokollierung [7]

Java 11 verfügt über ein gemeinsames Protokollierungssystem für alle JVM-Komponenten. Dank dieses einheitlichen Protokollierungssystems kann der Benutzer definieren, welche Komponenten in welchem Umfang protokolliert werden sollen. Diese differenzierte Protokollierung ist hilfreich für die Ursachenanalyse bei JVM-Abstürzen sowie für die Diagnose von Leistungsproblemen in einer Produktionsumgebung.

Profilierung mit geringem Aufwand [8]

Java Virtual Machine Tool Interface (JVMTI) wurde eine neue API zur Stichprobenentnahme für Java-Heapzuordnungen hinzugefügt. Die Stichprobenentnahme verursacht nur einen geringen Mehraufwand und kann kontinuierlich aktiviert werden. Die Heapzuordnung kann zwar mit Java Flight Recorder (JFR) überwacht werden, die Stichprobenentnahme in JFR funktioniert jedoch nur für Zuordnungen. Darüber hinaus kann es bei der JFR-Implementierung vorkommen, dass Zuordnungen nicht erkannt werden. Im Gegensatz dazu kann die Heapstichprobenentnahme in Java 11 Informationen zu aktiven und inaktiven Objekten bereitstellen.

Dieses neue Feature wird zunehmend von APM-Anbietern (Application Performance Monitoring, Anwendungsleistungsüberwachung) genutzt, und die Java-Entwicklungsgruppe untersucht das Verwendungspotenzial dieses Features in Kombination mit Azure-Leistungsüberwachungstools.

StackWalker [9]

Bei der Protokollierung wird häufig eine Momentaufnahme des Stapels für den aktuellen Thread abgerufen. Die Frage ist jedoch, wie viel von der Stapelüberwachung protokolliert werden soll (falls überhaupt). Es kann beispielsweise sein, dass die Stapelüberwachung nur für eine bestimmte Ausnahme einer Methode von Interesse ist. Die in Java 9 hinzugefügte StackWalker-Klasse liefert eine Momentaufnahme des Stapels und bietet Methoden, mit denen Programmierer die Nutzung der Stapelüberwachung präzise steuern können.

Garbage Collection [10]

Die folgenden Garbage Collectors sind in Java 11 verfügbar: Serial, Parallel, Garbage-First und Epsilon. Der Standard-Garbage Collector in Java 11 ist der Garbage-First-Garbage Collector (G1GC).

Der Vollständigkeit halber werden hier noch drei weitere Collectors erwähnt. Der Z-Garbage Collector (ZGC) ist ein gleichzeitiger Collector mit geringer Wartezeit, der versucht, Pausenzeiten unter 10 ms zu halten. ZGC ist als experimentelles Feature in Java 11 verfügbar. Der Shenandoah-Collector verkürzt GC-Pausenzeiten, indem mehr Garbage Collections gleichzeitig mit dem aktiven Java-Programm ausgeführt werden. Shenandoah ist ein experimentelles Feature in Java 12. Es sind jedoch Backports für Java 11 verfügbar. Der CMS-Collector (Concurrent Mark and Sweep, gleichzeitiges Markieren und Bereinigen) ist zwar verfügbar, aber seit Java 9 als veraltet eingestuft.

JVM legt GC-Standardwerte für den durchschnittlichen Anwendungsfall fest. Diese Standardwerte sowie andere GC-Einstellungen müssen meist optimiert werden, um den bestmöglichen Durchsatz oder die bestmögliche Wartezeit für die Anforderungen der Anwendung zu erreichen. Für eine ordnungsgemäße GC-Optimierung sind umfassende GC-Kenntnisse erforderlich. Dieses Know-how wird von der Java-Entwicklungsgruppe von Microsoft bereitstellt.

G1GC

Der Standard-Garbage Collector in Java 11 ist der G1-Garbage Collector (G1GC). G1GC ist für ein ausgewogenes Verhältnis zwischen Wartezeit und Durchsatz konzipiert. Der G1-Garbage Collector versucht, einen hohen Durchsatz zu erzielen, indem Pausenzeitenziele mit hoher Wahrscheinlichkeit erreicht. G1GC ist so konzipiert, dass vollständige Sammlungen vermieden werden, aber wenn die gleichzeitigen Sammlungen den Arbeitsspeicher nicht schnell genug zurückfordern können, tritt ein voller GC auf. Die vollständige Garbage Collection verwendet die gleiche Anzahl paralleler Arbeitsthreads wie die neuen und gemischten Garbage Collections.

Paralleler GC

Der parallele Garbage Collector ist der Standard-Garbage Collector in Java 8. Bei dem parallelen GC handelt es sich um einen Durchsatz-Garbage Collector, der mehrere Threads verwendet, um die Garbage Collection zu beschleunigen.

Epsilon [11]

Der Epsilon-Garbage Collector kümmert sich um Zuordnungen, gibt aber keinen Arbeitsspeicher frei. Ist der Heap erschöpft, wird JVM heruntergefahren. Epsilon eignet sich für kurzlebige Dienste sowie für Anwendungen ohne „Datenmüll“.

Verbesserungen für Docker-Container [12]

Vor Java 10 wurden für einen Container festgelegte Arbeitsspeicher- und CPU-Einschränkungen von JVM nicht erkannt. In Java 8 legt JVM beispielsweise die maximale Heapgröße standardmäßig auf ¼ des physischen Speichers des zugrunde liegenden Hosts fest. Ab Java 10 verwendet JVM von Containersteuerungsgruppen (cgroups) festgelegte Einschränkungen, um Arbeitsspeicher- und CPU-Limits festzulegen (siehe Hinweis weiter unten). Die standardmäßige maximale Heapgröße beträgt zum Beispiel ¼ des Container-Arbeitsspeicherlimits (also beispielsweise 500 MB für „-m2G“).

Darüber hinaus wurden JVM-Optionen hinzugefügt, mit denen Docker-Containerbenutzer präziser steuern können, wie viel Systemspeicher für den Java-Heap verwendet wird.

Diese Unterstützung ist standardmäßig aktiviert und steht nur auf Linux-basierten Plattformen zur Verfügung.

Hinweis

Der Großteil der Arbeit der Containersteuerungsgruppen wurde im Zuge von „jdk8u191“ zu Java 8 zurückportiert. Weitere Verbesserungen werden allerdings nicht unbedingt zu Java 8 zurückportiert.

Multi-Release-Jar-Dateien [13]

In Java 11 kann eine JAR-Datei mit mehreren Java-releasespezifischen Versionen von Klassendateien erstellt werden. Dank JAR-Dateien mit mehreren Releases können Bibliotheksentwickler mehrere Versionen von Java unterstützen, ohne mehrere Versionen von JAR-Dateien bereitstellen zu müssen. Nutzer dieser Bibliotheken müssen dank JAR-Dateien mit mehreren Releases keine passenden spezifischen JAR-Dateien für spezifische Runtimeziele mehr verwenden.

Sonstige Leistungsverbesserungen

Die folgenden JVM-Änderungen wirken sich direkt auf die Leistung aus:

  • JEP 197: Segmentiertes Codecache [14] – Unterteilt den Codecache in verschiedene Segmente. Diese Segmentierung ermöglicht eine bessere Steuerung des JVM-Speicherbedarfs, verkürzt die Überprüfungszeit kompilierter Methoden, sorgt für eine erhebliche Verringerung der Fragmentierung des Codecaches und verbessert die Leistung.

  • JEP 254: Kompakte Zeichenfolgen [15] – Ändert die interne Darstellung einer Zeichenfolge von einer Zeichenfolge pro Zeichen auf ein oder zwei Bytes pro Zeichen, je nach Codierung von Zeichen. Da die meisten Zeichenfolgen ISO-8859-1-/LATIN-1-Zeichen enthalten, halbiert sich durch diese Änderung der Speicherplatzbedarf für Zeichenfolgen.

  • JEP 310: Anwendung Class-Data Freigabe [16] – Class-Data Freigabe verringert die Startzeit, indem archivierte Klassen zur Laufzeit speicherzuordnungen ermöglicht werden. Die gemeinsame Nutzung von Anwendungsklassendaten erweitert die gemeinsame Nutzung von Klassendaten durch die Möglichkeit, Anwendungsklassen im CDS-Archiv zu platzieren. Wenn mehrere JVMs die gleiche Archivdatei nutzen, wird weniger Arbeitsspeicher beansprucht, und die Reaktionszeit des Systems verbessert sich.

  • JEP 312: Thread-Local Handshakes [17] – Ermöglicht die Ausführung eines Rückrufs auf Threads ohne Ausführen eines globalen VM-Safepoints, wodurch die VM eine geringere Latenz erreichen kann, indem die Anzahl der globalen Safepoints reduziert wird.

  • Lazy Allocation of Compiler Threads [18] – Im stufigen Kompilierungsmodus startet die VM eine große Anzahl von Compilerthreads. Dieser Modus ist die Standardeinstellung für Systeme mit vielen CPUs. Die Threads werden unabhängig vom verfügbaren Arbeitsspeicher oder der Anzahl von Kompilierungsanforderungen erstellt. Von Threads wird auch dann Arbeitsspeicher beansprucht, wenn sie sich im Leerlauf befinden (was größtenteils der Fall ist). Dies führt zu einer ineffizienten Ressourcennutzung. Zur Behebung dieses Problems wurde die Implementierung so geändert, dass beim Start nur ein einzelner Compilerthread jedes Typs gestartet wird. Das Starten zusätzlicher Threads und das Herunterfahren nicht verwendeter Threads wird dynamisch gehandhabt.

Die folgenden Änderungen an den Kernbibliotheken haben Auswirkungen auf die Leistung von neuem oder geändertem Code:

  • JEP 193: Variable Handle [19] – Definiert einen Standard, um die Äquivalente verschiedener java.util.concurrent.atomic und sun.misc.Unsafe-Vorgänge auf Objektfeldern und Arrayelementen, eine Standardmenge von Zaunvorgängen für die Feinkornsteuerung der Speicherfolge und einen Standard-Reichweiten-Zaunvorgang aufzurufen, um sicherzustellen, dass ein referenziertes Objekt stark erreichbar bleibt.

  • JEP 269: Convenience Factory-Methoden für Sammlungen [20] – Definiert Bibliotheks-APIs, um instanzen von Sammlungen und Karten mit kleinen Anzahl von Elementen bequem zu erstellen. Die statischen Factorymethoden für die Sammlungsschnittstellen zur Erstellung kompakter, unveränderbarer Sammlungsinstanzen. Diese Instanzen sind grundsätzlich effizienter. Die APIs erstellen kompakt dargestellte Sammlungen ohne Wrapperklasse.

  • JEP 285: Spin-Wait Hinweise [21] – Stellt API bereit, mit der Java auf das Laufzeitsystem hinweisen kann, das es sich in einer Spinschleife befindet. Bestimmte Hardwareplattformen profitieren davon, wenn ihnen von der Software signalisiert wird, dass ein Thread ausgelastet ist bzw. sich in einem Wartezustand befindet.

  • JEP 321: HTTP-Client (Standard) [22]- Stellt eine neue HTTP-Client-API bereit, die HTTP/2 und WebSocket implementiert und die ältere HttpURLConnection-API ersetzen kann.

Referenzen

[1] Oracle Corporation, "Java Development Kit 9 Release Notes", (Online). Verfügbar unter https://www.oracle.com/technetwork/java/javase/9u-relnotes-3704429.html. (Zugriff am 13. November 2019)

[2] Oracle Corporation, "Java Development Kit 10 Release Notes", (Online). Verfügbar unter https://www.oracle.com/technetwork/java/javase/10u-relnotes-4108739.html. (Zugriff am 13. November 2019)

[3] Oracle Corporation, "Java Development Kit 11 Release Notes", (Online). Verfügbar unter https://www.oracle.com/technetwork/java/javase/11u-relnotes-5093844.html. (Zugriff am 13. November 2019)

[4] Oracle Corporation, "Project Jigsaw", 22. September 2017. (Online). Verfügbar unter http://openjdk.java.net/projects/jigsaw/. (Zugriff am 13. November 2019)

[5] Oracle Corporation, "JEP 328: Flight Recorder", 9. September 2018. (online). Verfügbar unter http://openjdk.java.net/jeps/328. (Zugriff am 13. November 2019)

[6] Oracle Corporation, "Mission Control", 25. April 2019. (online). Verfügbar unter https://wiki.openjdk.java.net/display/jmc/Main. (Zugriff am 13. November 2019)

[7] Oracle Corporation, "JEP 158: Unified JVM Logging", 14. Februar 2019. (online). Verfügbar unter http://openjdk.java.net/jeps/158. (Zugriff am 13. November 2019)

[8] Oracle Corporation, "JEP 331: Low-Overhead Heap Profiling", 5. September 2018. (online). Verfügbar unter http://openjdk.java.net/jeps/331. (Zugriff am 13. November 2019)

[9] Oracle Corporation, "JEP 259: Stack-Walking API", 18. Juli 2017. (online). Verfügbar unter http://openjdk.java.net/jeps/259. (Zugriff am 13. November 2019)

[10] Oracle Corporation, "JEP 248: Make G1 the Default Garbage Collector", September 12, 2017. (online). Verfügbar unter http://openjdk.java.net/jeps/248. (Zugriff am 13. November 2019)

[11] Oracle Corporation, "JEP 318: Epsilon: A No-Op Garbage Collector", 24. September 2018. (online). Verfügbar unter http://openjdk.java.net/jeps/318. (Zugriff am 13. November 2019)

[12] Oracle Corporation, "JDK-8146115: Verbessern der Docker-Containererkennung und Ressourcenkonfigurationsnutzung", 16. September 2019. (online). Verfügbar unter https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115. (Zugriff am 13. November 2019)

[13] Oracle Corporation, "JEP 238: Multi-Release JAR-Dateien", 22. Juni 2017. (online). Verfügbar unter http://openjdk.java.net/jeps/238. (Zugriff am 13. November 2019)

[14] Oracle Corporation, "JEP 197: Segmentiertes Codecache", 28. April 2017. (online). Verfügbar unter http://openjdk.java.net/jeps/197. (Zugriff am 13. November 2019)

[15] Oracle Corporation, "JEP 254: Compact Strings", 18. Mai 2019. (online). Verfügbar unter http://openjdk.java.net/jeps/254. (Zugriff am 13. November 2019)

[16] Oracle Corporation, "JEP 310: Application Class-Data Sharing", 17. August 2018. (online). Verfügbar unter https://openjdk.java.net/jeps/310. (Zugriff am 13. November 2019)

[17] Oracle Corporation, "JEP 312: Thread-Local Handshakes", 21. August 2019. (online). Verfügbar unter https://openjdk.java.net/jeps/312. (Zugriff am 13. November 2019)

[18] Oracle Corporation, "JDK-8198756: Lazy allocation of compiler threads", Okt 29, 2018. (online). Verfügbar unter https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756. (Zugriff am 13. November 2019)

[19] Oracle Corporation, "JEP 193: Variable Handle", 17. August 2017. (online). Verfügbar unter https://openjdk.java.net/jeps/193. (Zugriff am 13. November 2019)

[20] Oracle Corporation, "JEP 269: Convenience Factory-Methoden für Sammlungen", 26. Juni 2017. (online). Verfügbar unter https://openjdk.java.net/jeps/269. (Zugriff am 13. November 2019)

[21] Oracle Corporation, "JEP 285: Spin-Wait Hinweise", August 2017. (online). Verfügbar unter https://openjdk.java.net/jeps/285. (Zugriff am 13. November 2019)

[22] Oracle Corporation, "JEP 321: HTTP Client (Standard)," September 27, 2018. (online). Verfügbar unter https://openjdk.java.net/jeps/321. (Zugriff am 13. November 2019)