Ausblick auf die Version 9 von Java 05.01.2017, 10:39 Uhr

Modularität, Portabilität und Produktivität

Java 9 bietet weniger neue Sprachmittel, doch dafür zahlreiche zentrale Verbesserungen
und hilfreiche Ergänzungen.
Zu den Verbesserungen und Ergänzungen gehören erstmals ein Modulkonzept für die gesamte Java-Plattform (JDK, JRE), neue Programmier-Tools und eine Optimierung der Speicherverwaltung.
Java als Plattform und Sprache hat inzwischen mehr als zwei Jahrzehnte auf dem Buckel und kann auf einen nachhaltigen Erfolg zurückblicken. In zahlreichen Marktanalysen zur Verbreitung und Popularität von Programmiersprachen nimmt Java immer noch eine Position an der Spitze ein (Bild 1).
Java nimmt seit vielen Jahren in Auswertungen über die Popularität von Programmiersprachen wie PYPL, Developer Survey Results (Stack Overflow), RedMonk oder TIOBE eine Spitzenposition ein (Bild 1)
Dies gilt vorwiegend für das Backend und Frontend von Desktop- und Web-Apps, derzeit jedoch weniger für die mobile Welt und das Internet der Dinge (Internet of Things/IoT).
In der mobilen Welt favorisierten Hersteller wie Apple (iOS) oder Google (Android) ihre eigenen proprietären Entwicklungsansätze. Analoges gilt im IoT – dort tummeln sich aktuell abhängig vom Anwendungsgebiet noch wesentlich mehr Softwarehäuser und Hardwarelieferanten. Leider entwickelte sich die Java-Plattform und damit die APIs im Lauf der Zeit durch verschiedene hardware- und anwendungsspezifische Varianten auseinander. Im Unterschied zu den genannten Plattformen besitzt Java mit Version 9 jetzt die Chance, sich stärker auf allen Zielumgebungen zu etablieren.

Write once – run anywhere

Auf dem klassischen Desktop entstand die Java SE (Standard Edition), im Unternehmensumfeld etablierte sich Java EE (Enterprise Edition), während Oracle im mobilen und Embedded-Bereich auf zwei abgespeckte Java MEs (Micro Editions) setzte. Daraus resultierten differierende JDKs und Laufzeitumgebungen. Mit Version 8 begann die Vereinheitlichung von Java auf der Basis zahlreicher Spracheigenschaften, die man durchgängig für alle genannten Java-Editions verfügbar machte. Zudem nahm Oracle eine Restrukturierung der kompletten Codebasis zu einer einheitlichen Plattform vor.
Dieses Vorgehen erleichtert zukünftig die Programmierung plattformübergreifenden Quellcodes und erhöht im Unterschied zur Vergangenheit wesentlich die Portabilität. Mit der neuen Version 9 stellt Oracle der Java Community die wohl wichtigsten Features seit dem Entstehen des Java-Ökosystems bereit. Mit diesen Neuerungen wandelt sich die Java-Plattform selbst zu einer echten portablen und vor allem flexib­len Entwicklungsumgebung, die es erleichtert, Anwendungen und Dienste entsprechend den vorliegenden Anforderungen zu modularisieren (Bild 2). Damit erfüllt Java wichtige Kriterien des Software-Engineering für eine effiziente und produktive Entwicklung.
Oracle reduzierte für die Einführung eines Modulsystems die Abhängigkeiten innerhalb der Java-Plattform und erreichte damit eine echte modulare Anwendungsentwicklung für die Programmiersprache Java (Bild 2)
Schließlich erlaubt erst diese Modularisierung des JDK, auch die JRE optimal auf die individuellen, spezifischen Bedürfnisse einer konkreten Hardware abzustimmen. Diese Vorgehensweise haucht gewissermaßen dem schon länger totgesagten Ansatz des Write once, run anywhere (WORA) erneutes Leben ein. Zudem zwang die zunehmende Weigerung der Browserhersteller, einige Plug-in-Technologien wie Flash, Silverlight oder Java noch länger zu unterstützen, Oracle dazu, mit der neuen Version 9 das Applet-API als deprecated zu erklären. Somit dürfte sich auch der Leidensspruch eines jeden Java-Programmierers, Write once, debug/test everywhere, automatisch abschwächen.

Endlich im Software-Engineering angekommen

Leider besteht in der Praxis kein Zusammenhang zwischen Einsatzbreite beziehungsweise Verbreitungsgrad einer Programmiersprache und deren Qualität beziehungsweise Eignungsgrad in der Anwendungsentwicklung. Eine wesentliche Voraussetzung seitens des Software-Engineering an eine Sprache stellt die Unterstützung eines Modulsystems dar. Diese Erkenntnis hatte schon Sun und dann auch Oracle seit vielen Jahren gewonnen und sich öffentlich seit Version 7 dazu bekannt. So arbeiteten Oracle und die Mitglieder des OpenJDK-Projekts in verschiedenen Projekten an der Implementierung eines Modulsystems für Java.
Bereits im JDK 7 wollte man mittels Superpackages ein spezielles Sprachkonstrukt für Module einführen und diese über verschiedene JSRs (Java Specification Requests) realisieren. Jedoch erkannte man im Rahmen der Umsetzungsbemühungen, dass eine schnelle Lösung der gravierenden Bedeutung eines Modulsystems nicht gerecht wird. Aus diesem Grund initiierte das OpenJDK-Team unter Federführung von Oracle ein eigenständiges Projekt Jigsaw. Auf der Basis bisheriger Arbeitsergebnisse der JSRs sollte ein Java Platform Module System und mittels neuer JDK Enhancement Proposals (JEPs) ein modulares JDK entstehen.
Nach mehrjähriger Entwicklungszeit und parallelen Reviews im Rahmen aktuell laufender OpenJDK-Aktivitäten (Java Version 7, 8 und 9) gelang dem Jigsaw-Projekt die Rea­lisierung eines einzigartigen Modulsystems. Dieses erfüllt mit Version 9 von Java wichtige und für die Plattform und deren weitere zukünftige Verbreitung in der Praxis zentrale Anforderungen:
  • Flexibilität und Skalierbarkeit: Restrukturierung des Quellcodes und neue, spezielle Werkzeuge versetzen Entwickler in die Lage, mittels einer einheitlichen Java SE auf die eigenen Bedürfnisse abgestimmte JREs für spezifische Zielumgebungen bereitzustellen.
  • Sicherheit und Wartbarkeit: Die Restrukturierung führte teilweise zu Neuimplementierungen; so musste das bisherige Security-System neu realisiert werden. Gleichzeitig gewährleistete man für die Supported APIs eine höhere Kompatibilität – eine Anwendung, die nur Supported APIs verwendet und mit Java Release N lauffähig ist, soll dies auch für den Nachfolger N+1 gewährleisten, sogar ohne erneute Übersetzung.
  • Bessere Unterstützung der Software-Entwicklung: Neue und überarbeitete JDK-Werkzeuge sollen die Programmierung, Wartung, Portierung und Verteilung insbesondere großer Anwendungslandschaften erleichtern.
  • Höhere Performance: Verschiedene Benchmarks zeigten bereits für die Early-Access-Releases (EA) von Java 9 Verbesserungen im Laufzeitverhalten. Dies betrifft sowohl das Übersetzen mit neuen Compilertechniken wie Smart Java und Ahead-of-Time Compilation (AOT) als auch das Laufzeitverhalten (segmentierter Code-Cache, überarbeitete Klassen).
In der Vergangenheit war die größte Schwäche der Java-Technologie die ständig ansteigende Größe einer Java Runtime. Obwohl Java 8 sogenannte Compact Profiles einführte, konnte man die JRE nicht noch weiter in eine für die Ziel­umgebung und Anwendung geeignetere Untermenge teilen. Eine Java-Installation umfasste immer alle für die jeweilige Plattform vorgesehenen Bibliotheken (beispielsweise die APIs zu SQL, Swing oder XML), selbst wenn die Anwendung diese APIs nicht benötigte. Dieser Ballast erschwerte den gesamten Entwicklungsprozess und erhöhte wesentlich die Kosten, auch in der Produktion zur Laufzeit.
Bis zur Version 8 kannte die Java-Plattform lediglich eine Datenkapselung (Encapsulation) im Sinne von Parnas: Spezielle Sprachkonstrukte (private, public, default, protected) verbergen Daten in (abstrakten) Klassen und Interfaces vor einem äußeren Zugriff. Dabei unterbindet das Schlüsselwort private den direkten Zugriff auf interne Java-Datenstrukturen, während public einen Zugriff auf ausgewählte Datenstrukturen über definierte Schnittstellen erlaubt (Bild 3). Dieses Geheimnisprinzip (Information-Hiding) bietet lediglich Zugriffsschutz; berücksichtigt aber nicht Fragestellungen zur Laufzeit wie Shadowing oder Versionierung.
Kapselung stellt eine einheitliche Kommunikation über definierte Schnittstellen mit dem Objekt sicher, gemäß den Prinzipien Information-Hiding (Datenkapselung) und dem Black-Box-Modell des Software-Engineering (Bild 3)
Version 9 der Java-Plattform erweitert das Geheimnisprinzip (Information ­Hiding) einer Kapselung von Objekten beziehungsweise Klassen auf eine Kapselung zur Laufzeit für ganze Bibliotheken mittels Modulen. Dies erfolgt anhand neuartiger Konstrukte der Sprache und ergänzender Werkzeuge, um schon während der Entwicklung möglichst produktiv zu arbeiten. Im Mittelpunkt steht der Begriff des Moduls, das aus einem Schnittstellen- und einem Implementierungsteil besteht. Dem Nutzer eines Moduls bleibt die Implementierung verborgen, er muss nur die Schnittstelle kennen. Aus Sicht der Architektur einer Anwendung stellt ein Modul eine unabhängig verteilbare Einheit (Deployment Unit) dar.

Java-Module definieren und deren Services nutzen

Aus dem Blickwinkel der Programmierung betrachtet stellt ein Modul eine Gruppierung von Code dar – in der Java-Welt entspricht dieser Gruppierung einer Sammlung von Packages. Ein Modul enthält somit Referenzen auf Java-Quellcode, betriebssystem- und maschinenspezifische Dateien, Entwicklungsressourcen oder Konfigurationsdateien. Die Deklaration eines Moduls erfolgt in einer Datei mit dem Namen module-info.java; diese module-info.java-Datei befindet sich in einem Verzeichnis, das den Namen des Moduls trägt. Die zu einem Modul gehörenden Bestandteile wie Java-Quellcode, Ressourcen und Ähnliches befinden sich standardmäßig unterhalb des Modul-Verzeichnisses:

/*
* In der Datei module-info.java steht die Modul-Deklaration
* Die module-Anweisung gibt dem Modul
* den Namen com.firma.awse
* Das Verzeichnis src\com.firma.awse (Windows)
* beziehungsweise * src/com.firma.awse (Mac OS X, Linux)
* enthaelt die module-info.java-Datei (Deklarationsdatei)
* Den zum Modul com.firma.awse gehoerenden Quellcode
* findet man unter Windows in den Unterverzeichnissen
* src\com.firma.awse unter anderem findet man dort
*    src\com.firma.awse\sysa\kunde
*    src\com.firma.awse\sysa\produkt
*/
module com.firma.awse {
//   hier die benoetigten Moduln auflisten
     requires sysb.lager;
     requires sysb.bestell;
 
//  hier die Dienstleistungen des Moduls verfuegbar machen
    exports sysa.kunde;
    exports sysa.produkt;
}
Den Namen eines Moduls kann man beliebig wählen; um Eindeutigkeit sicherzustellen, sollte man sich am URL-Namensschema des zugehörigen Package orientieren. Die requires-Anweisungen verdeutlichen, welche anderen Module das aktuelle Modul für eine Übersetzung und Ausführung benötigt, während die exports-Anweisungen die Schnittstelle des Moduls extern zugänglich macht, das heißt, die für andere Module öffentlichen Typen des Package festlegt.
Den Quellcode des Moduls se.callista.java9.modules.common findet man in den beiden Unterverzeichnissen api und internal (Bild 4)
Ergänzend zum öffentlich zugänglichen API eines Moduls enthält dessen Ablagestruktur auch die nur internen, nicht exportierten Packages und deren Quellcode (Bild 4). Natürlich kann man Module in einer beliebig anderen Verzeichnisstruktur ablegen. Das standardmäßig vorgesehene Layout bietet jedoch den Vorteil, durch Angabe nur eines Suchpfads alle Module mit einem einzigen Compilerlauf übersetzen zu können. Soll ein Modul lediglich zur Laufzeit sichtbar sein, fügt man der requires-Anweisung die optionale Angabe for reflection hinzu; soll es nur zur Compilezeit verfügbar sein, drückt dies der Zusatz for compilation aus.

Teamarbeit und Software-Marketing

Der Java-Compiler überführt eine Modul-Deklarationsdatei (module-info.java) wie eine herkömmliche .java-Quelldatei in eine module-info.class-Datei, den sogenannten Modul-Deskriptor. In Java 9 übersetzt die neue javac-Option --module-source-path (Modul-Source-Pfad) den Quellcode des Moduls und alle von ihm benötigten Module, vorausgesetzt, diese liegen ebenfalls im Quellcode vor. Somit kompiliert ein einziges Kommando alle Module einer Anwendung und legt die
CLASS-Dateien strukturiert in einem einheitlichen Ausgabeverzeichnis ab:

# Alle .java-Quelldateien eines Moduls
# inklusive der benoetigten Module uebersetzen
# und im Ordner build ablegen
$ javac -d build --module-source-path src \
  $(find src -name "*.java")
Liegen Module lediglich in übersetzter Form als CLASS-Dateien (Bytecode) vor, so greift man auf den in Java 9 eingeführten --module-path (kurz: --mp) zurück. Diese Compiler-Op­tion kompiliert den Quellcode eines Moduls, ohne dass für die anderen benötigten Module der Quellcode vorliegen muss. Für den Java-9-Compiler reicht der Bytecode (CLASS-Dateien) aller benötigten Module aus, um das eigene Modul zu übersetzen:

# Das Modul com.firma.awse unter Einbezug
# des Bytecodes der benoetigten Module uebersetzen
$ javac --module-path build -d build/com.firma.awse \
  src/com.firma.awse/module-info.java
Dadurch ermöglicht der Modulpfad eine arbeitsteilige Realisierung eines Anwendungssystems, sodass man einzelne Module als Arbeitspakt verschiedenen Mitgliedern eines Entwicklungs- oder Projektteams zuordnen kann. Gleichzeitig schafft der Modulpfad die Voraussetzung, ganze Java-Module als eigenständige Bibliotheken auf dem Softwaremarkt zu vertreiben. Zudem schützt das neue Modulkonzept geistiges Eigentum, da dessen Quellcode nicht offen zugänglich sein muss.
Neue Version mit passendem Nummernschema
Das Schema zur Nummernvergabe für eine JDK-Version wurde im Lauf der Zeit seitens Sun/Oracle immer wieder einmal geändert.
So entschloss sich Sun, die JDK-Versionen 1.2 bis 1.5 unter
der Bezeichnung Java 2 (Kurzform: J2SE) zu vermarkten. Ab der Version 1.5 erkannte auch Sun, dass die Bezeichnung Java 2 eher verwirrend war, und benannte diese kurzerhand in Java 5 um.
Ab Version 1.6 ließ man die hinter dem Produktnamen Java 2 stehende Idee insgesamt fallen. So erhielten die JDK-Version 1.7 beziehungsweise JDK 1.8 die Namen Java 7 beziehungsweise Java 8. Jedoch gab im Unterschied zum Java-Produktnamen die JVM als Versionsnummer immer noch die Zahl 1 aus. So entsprach Java 7 der Versionsnummer 1.7 und Java 8 der Versionsnummer 1.8.
Java 9 behebt nun diesen Missstand. Sämtliche Version-Strings, die innerhalb der JVM vorhanden sind, basieren auf dem Schema 9.x.x, sodass die bisher unverständliche 1 vor der JDK-Version entfällt.
Gegenüber den Vorgängerversionen ändern sich mit ­Java 9 die Regeln für die Sichtbarkeit von Namen. Bis zur Version 8 (Bild 5) kann man auf jede Deklaration, die als public gekennzeichnet ist, ohne zusätzliche Vorkehrungen direkt im Quellcode und zur Laufzeit über den Klassenpfad (classpath) zugreifen. Version 9 bietet Entwicklern erstmals eine modulare Programmierung und implementiert hierzu eine strenge Datenkapselung (strong Encapsulation). Nur als public deklarierte Namen, die sich in einem exportierten Package eines Moduls befinden, können von anderen Modulen angesprochen werden.
In Java schränken Zugriffs- und Sichtbarkeitsmodifizierer (Access/Visibility Modifier) die Rechte auf andere Objekte ein (Kapselung) oder verlangen von deren Subklasse ein bestimmtes Verhalten  (Vererbung/Abstraktion) (Bild 5)
Das nach der Übersetzung folgende Packaging für die Softwareverteilung (Deployment) erfolgt in Java 9 unter Einbezug der Module. Wie bisher erzeugt das Java Archive-Tool (jar) aus allen CLASS-Dateien (inklusive dem Modul-Deskriptor) und den dort referenzierten Ressourcen ein JAR (Java Archive). Für die zugehörige JAR-Datei verwendet man in der Java-9-Terminologie den Begriff eines modular JAR (Modul-JAR). Dabei handelt es sich um eine gewöhnliche JAR-Datei, die in ihrem Wurzelverzeichnis zusätzlich eine module-info.class-Datei enthält.
Alternativen für Java-Applets
Da zukünftige Java-Plattformen nach Version 10 nicht mehr das Netscape-Plug-in-API-Protokoll (NPAPI) für Applets unterstützen, sollte man baldmöglichst Alternativen nutzen:
  • Java Web Start: Mit J2SE 1.4 stellte Oracle erstmals die Java-Web-Start-Technologie vor, um Java-Anwendungen unabhängig vom Webbrowser über HTML-Links zu starten und auch Updates durchzuführen. Neben einer Portabilität über Web­browser hinweg bietet Java Web Start die Möglichkeit, ganz gezielt einen bestimmten Release der Java-Plattform zu nutzen.
  • Native Betriebssystem-Installer: Diese eignen sich nur für Desktop-Anwendungen, die keine zentrale Administration benötigen und eine auf dem jeweiligen Betriebssystem installierte JRE nutzen können.
  • WebView-Komponente von JavaFX: Dabei handelt es sich um die auf WebKit basierte Embedded-Browsertechnologie von JavaFX. WebView unterstützt vollständig CSS, JavaScript, DOM und HTML5.
Diese Konzeption für den Aufbau eines Java-Archivs und ein neuer Lademechanismus für Modul-JARs gewährleisten die Abwärtskompatibilität (Backward Compatibility) einer JAR-Datei. Da module-info.java keinem gültigen Namen entspricht, ignorieren ältere JVMs einfach die übersetzten module-info.class-Dateien. Eine JVM ab Version 9 greift zur Ausführung einer JAR-Datei auf den Modulpfad (java --mp) zurück. In Anlehnung an den alten Klassenpfad (classpath) gibt man über den Modulpfad JARs beziehungsweise ganze Verzeichnisse an, aus denen Module geladen werden.
Ferner erhält das jar-Tool mit Java 9 eine Fülle neuer Parameter und Optionen, die der Befehl jar --help in einem Terminalfenster anzeigt (Bild 6).
Das Java Archive-Tool (jar) besitzt in Version 9 eine Vielzahl neuer Aufrufparameter und Optionen (Bild 6)
So gibt die Option --print-module-descriptor für ein modulares JAR den Modul-Deskriptor aus. Um den Inhalt eines build-Ordners in eine JAR-Datei zu packen, greift man auf den nachfolgenden Befehl zurück; dabei ist die Angabe der Startklasse über den Aufrufparameter (--main-class) optional:

# Ausgehend von den .class-Dateien erstellt das
# jar-Tool im Verzeichnis mlib das Java-Archiv
# Anwnd.jar.
# Die Startklasse stellt com.firma.awse.Verkauf dar.
$ cd build
$ jar --create --file mlib/Anwnd.jar \
  --main-class com.firma.awse.Verkauf
Um eine Koexistenz herkömmlicher und modularer JARs zu gewährleisten, implementiert Java 9 nachfolgende Vorgehensweise: Übergibt man einer JVM im Modulpfad JARs ohne Moduldeskriptor (bisher klassisches JAR), so wird zur Laufzeit ein Moduldeskriptor automatisch generiert. Man spricht dann von einem automatischen Modul (Automatic Module), dieses exportiert alles (exports all) und wird allen anderen Modulen als Abhängigkeit hinzugefügt (requires all). Im Unterschied dazu bezeichnet man Module eines modularen JAR als explizite (benannte) Module. Sollen alle JARs eines Klassenpfads (classpath) als Module zugreifbar sein, so erzeugt man zur Laufzeit sogenannte Unnamed Modules (requires all).
OpenJDK, JEPs, JSRs und JCPs
Das OpenJDK-Projekt setzt JSRs als Referenzimplementierung um; Hersteller überprüfen Implementierungen mittels der Compatibility Test Suite
Seit mehr als zehn Jahren favorisieren Sun und danach Oracle mittels der OpenJDK-Community eine Open-Source-Implementierung der Java Platform, Standard Edition (Java SE):
  • OpenJDK: Das OpenJDK-6-Projekt stellte auf Basis von JDK 7 erstmals eine Open-Source-Version (primär abgestimmt auf die Anforderungen von Java 6) vor. Die OpenJDK-Implementierung dient allen Beteiligten als eigenständige, frei zugängliche Referenzplattform.
  • JEP: Ein JDK Enhancement Proposal entspricht einem seitens Oracle getriebenen Prozess, der sich mit wichtigen neuen Anforderungen an das JDK beschäftigt und – entsprechende Eignung vorausgesetzt – zukünftig in einem JSR endet.
  • JSR: Ein Java Specification Request beschreibt in formal aufgebauten Dokumenten neue, für die Java-Plattform vorgeschlagene Spezifikationen oder Technologien.
  • JCP: Ein Java Community Process definiert eine Vorgehensweise zur Bereitstellung von JSRs; er führt vor der offiziellen Freigabe eines JSR öffentliche Reviews mit den JCP-Mitgliedern durch.

Parallel zur Modularisierung der Java-Plattform gruppierte Oracle die JDK-APIs in verschiedene Kategorien. Diese Gruppen helfen Programmierern, die Portierung einer Anwendung auf Java 9 und die nachfolgenden Versionen in Einzelschritten vorzubereiten und so insgesamt zu erleichtern:
  • Supported: Nur diese API-Kategorie ist für den externen Gebrauch von Entwicklern in eigenen Programmen vorgesehen. Zu ihnen gehören alle vom JCP verabschiedeten Standard-APIs des JDK wie java.* oder javax.*. Zusätzlich kommen einige JDK-spezifische APIs wie com.sun.* oder jdk.* hinzu.
  • Unsupported: Bei dieser API-Kategorie handelt es sich um JDK-interne APIs, die man nicht extern in eigenen Anwendungen verwenden sollte; sie umfassen die meisten sun.*-Packages.
Die letzte API-Kategorie der unsupported APIs, also die JDK-internen APIs, unterteilt Oracle in zwei weitere Gruppen. Für beide Gruppen zeigt Oracle frühzeitig mögliche Migrationspfade für Anwendungen auf, um auf diese Weise Programmierer bestmöglich zu unterstützen:
  • Non-critical: Diese Kategorie findet außerhalb des JDK in Anwendungen keine Verbreitung, höchstens zu Informa­tionszwecken. Version 9 kapselt diese in eigenständige Module und nutzt diese nur für interne Zwecke.
  • Critical: Sie beinhalten Funktionen, die man außerhalb des JDK nur schwer, wenn überhaupt, implementieren kann. Zu ihnen zählen sun.misc.* und sun.reflect.*. JDK 9 erklärt die critical internal APIs als deprecated (veraltet). Es ist geplant, die in Version 9 noch benutzten critical APIs ab Version 10 vollständig zu beseitigen.
Das JDK Enhancement Proposal 260 beschreibt alle kritischen internal APIs, die so lange in zukünftigen Java-Versionen verfügbar bleiben, bis für sie ein Ersatz geschaffen wurde. Um eigene Anwendungen auf den Gebrauch derartiger APIs zu untersuchen, enthält das JDK seit Version 8 den ­Java Class Dependency Analyzer jdeps. Dieses Tool analysiert auch Abhängigkeiten von JARs und Modulen.
OSGi – OSGi Service Platform
Die OSGi Alliance unterstützt mittels der OSGi Service Platform die Modularisierung von Java-Anwendungen nicht nur zum Entwicklungszeitpunkt, sondern auch zur Laufzeit unter anderem mit folgenden Features:
  • Komponenten/Module: OSGi diente zur Realisierung von Java-­Komponenten (Module, Bundles/Services). Auf der Ebene von Packages deklariert man Abhängigkeiten zwischen den Komponenten.
  • Shadowing: Besitzen zwei Objekte denselben Namen, kennt man aus der Softwaretechnik verschiedene Strategien für den Zugriff auf das gewünschte Objekt. Auch OSGi verfügt über verschiedene Strategien zur Auflösung (Resolution) von Namensgleichheiten auf Modul-Ebene.
  • Versionierung: OSGi umfasst ein Versionskonzept unter anderem für den gleichzeitigen Betrieb mehrerer Versionen eines Moduls.
Version 9 bringt für jdeps einige Verbesserungen und Erweiterungen, um alle Klassen eines JAR, einer CLASS-Datei, eines Verzeichnisses oder eines voll qualifizierten Klassen­namens zu analysieren.

Eine Vielzahl von Aufrufparametern

Zudem besitzt jdeps eine Vielzahl von Aufrufparametern und unterstützt auch Filter, um die Ergebnisse einer Abhängigkeitsanalyse bedarfsorientiert einzuschränken; auch über Muster mittels regulärer Ausdrücke kann gesucht werden. Leider kennt jdeps in Java 9 wie bisher nur statische Abhängigkeiten. Dynamische Abhängigkeiten, die zur Laufzeit entstehen, bleiben nach wie vor bei der Analyse unberücksichtigt. Bei jdeps handelt es sich also um ein sogenanntes statisches Analysewerkzeug.
Ein Modul-Readability-Graph entsteht durch einen Modul-Abhängigkeits-Graphen, indem implizit vorhandene Lesebeziehungen zwischen den Modulen berücksichtigt werden. Um eine Lesbarkeit beziehungsweise einen Zugriff für ein Modul an ein anderes implizit weiterzureichen, besitzt Java 9 für die Moduldeklaration bei der requires-Anweisung einen optionalen public-Modifier. Ein requires public eines Moduls A in der Deklaration des Moduls Z bedeutet, dass jedes anderes Modul B über eine requires-Anweisung auf Z auch lesend auf Modul A zugreifen kann.
Sichtbarkeit von Namen in Java 9
Mit der neuen Java-Version ändern sich grundlegend die Regeln für Zugriffe auf public-deklarierte Namen:
  • Mit public deklarierte Namen, die ein Modul exportiert, sind für alle sichtbar und damit direkt verwendbar.
  • Führt ein Modul lediglich ein exports von Packages für bestimmte Module durch (exports packages_names to module_names), so beschränkt sich die Sichtbarkeit auf die nach to genannten Module (Qualifizierter Export / Qualified Export).
  • Auf alle übrigen (die bisherigen) public-Deklarationen können lediglich andere Klassen, die sich innerhalb desselben Moduls befinden, zugreifen.
Ergänzend zur losen Kopplung von Modulen über eine implizite Lesebeziehung mittels requires public kennt Java auch eine lose Kopplung von Programmen über Service-Interfaces und Service-Provider. Über den Klassenpfad (classpath) findet die JVM zur Laufzeit einen passenden Service-Provider.
Ein Service-Interface macht man in einer Modul-Deklaration über eine provides … with-Anweisung anderen Modulen verfügbar. So definiert in einer Modul-Deklaration die Anweisung

provides java.sql.Driver with com.mysql.jdbc.Driver;

ein Service-Interface java.sql.Driver über den Service-Pro­vider com.mysql.jdbc.Driver. Diese Vorgehensweise legt bereits vor der Ausführung eines Programms eine versteckte Implementierungsklasse für das Modulsystem offen. Beide Konstrukte requires public und provides … with dienen dem Compiler als qualitätssichernde Maßnahmen bereits bei der Übersetzung einer Anwendung, um spätere Laufzeitfehler bei der Programmausführung auf ein Minimum zu beschränken.

Analyse von Modul-Graphen

Über die Option -help gibt jdeps für sämtliche verfügbaren Aufrufparameter eine Zusammenfassung in einem Terminalfenster aus. Zusätzliche Informationen erhält man auf der für jdeps speziell eingerichteten Homepage. Zur Planung der Migration einer Anwendung auf das JDK 9 sollte man diese mittels jdeps und der Option jdkinternals analysieren, um Abhängigkeiten von internal APIs zu erkunden. Für alle nicht in JEP 260 aufgeführten APIs benötigt man baldmöglichst einen Ersatz, da diese in Java 9 für die Programmierung von Anwendungen generell nicht mehr zugänglich sind.
Um die mittels einer jdeps-Analyse ermittelten Abhängigkeiten in eine DOT-Datei zu schreiben, steht die Option -dot­output <verzeichnis> zur Verfügung. jdeps erzeugt im spezifizierten Ordner <verzeichnis> für jede zu analysierende CLASS- oder JAR-Datei eine eigene DOT-Datei mit den zugehörigen Abhängigkeiten. Zusätzlich erstellt das Analyse-Tool eine summary.dot-Datei, in der alle Abhängigkeiten zusammenfassend aufgeführt sind (Bild 7).
Eine Analyse von javafx-swt.jar mit jdeps und der Ausgabeoption -dotoutput erzeugt zwei DOT-Dateien: javafx-swt.jar.dot und summary.dot (Bild 7)
Ein so erzeugte DOT-Datei dient als Grundlage, um die Abhängigkeiten über eine Grafiksoftware (zum Beispiel Graphviz) visuell darzustellen. Die grafische Darstellung des Modul-Graphen erleichtert die Planung von Einzelschritten für die Migration einer Anwendung. Zur Vorbereitung einer Migration stellen die nachfolgenden Aufrufoptionen von jdeps hilfreiche Informationen oder gar weiterverwendbare Arbeitsergebnisse bereit:
  • list-deps: Gibt die Abhängigkeiten und die verwendeten JDK-internen APIs aus.
  • list-reduced-deps: Arbeitet wie -list-deps, berücksichtigt aber keine impliziten Lesebeziehungen.
  • add-modules <modul-namen>: Fügt die übergebenen Module zur Menge der bisher zu untersuchenden CLASS/JAR-Dateien hinzu.
  • check <modul-namen>: Analysiert die in den aufgeführten Modulen enthaltenen Abhängigkeiten, gibt deren Modul-Deskriptor aus und identifiziert nicht verwendete qualifizierte Exporte.
  • generate-module-info <verzeichnis>: Erzeugt eine module-info.java-Datei für die im jdeps-Befehl genannten JAR-Dateien und legt diese im genannten Verzeichnis ab.
Version 9 des JDK enthält erstmals als eigenständiges Tool ­einen Java-Linker: jlink genannt. Das Werkzeug baut Anwendungen mit minimaler Java Runtime, die ausschließlich verwendete (das heißt, vom Programm benutzte) Module aus der JDK-Modulmenge umfassen. Da der Java-Linker nur die wirklich benötigten Module verwendet, erzeugt er eine minimale Java-Laufzeitumgebung; jlink führt quasi eine Runtime-Optimierung durch. Greift etwa eine Anwendung nicht auf die XML-Funktionalität des JDK zurück, so kopiert jlink dieses Modul auch nicht in die Laufzeitumgebung.

Platzsparende Runtime

Ergebnis eines jlink-Aufrufs stellt eine platzsparende Runtime unter Berücksichtigung aller selbst programmierten und benutzten Anwendungsmodule dar; man spricht von einer möglichst kleinen ausführbaren Distribution. Außer Applikationsimages erzeugt der Java-Linker auch alle anderen für die Ausführung oder die Wiederverwendung von Bytecode erforderlichen Dateitypen (Bild 8). Ausgehend von CLASS-/JAR-/JMOD-Dateien generiert der Linker Modul-Bibliotheken (.jmod), Java-Archive (.jar), Java-Bibliotheken oder ausführbare Distributionen (JVM-Image).
Der Java-Linker verarbeitet drei verschiedene Bytecode-Typen und erzeugt aus ihnen je nach Zielrichtung verschiedene Byte­code-Typen zur Wiederverwendung (Bild 8)
Dem Java-Linker übergibt man mittels -module-path die aus dem JDK benötigten Module und macht ihm zusätzlich mit der Option -addmods die selbstprogrammierten Module zugänglich. Aus diesen über -module-path und -addmods zugeführten Modulen schreibt der Linker seine Arbeitsergebnisse in das über -output festgelegte Verzeichnis:

jlink --modulepath $JAVA_HOME/jmods:mlib --addmods
Verkauf \
  --output myimage
Sollte der Linker Module vermissen, so gibt er entsprechende Fehlermeldungen aus.
Java 9 mit durchgängigem Multi-Release-Support
Ein Multi-Release-JAR ­unterstützt gleichzeitig ­verschiedene ­Java-
Laufzeit­umgebungen.
Erstmals unterstützt Java 9 sogenannte Multi-Release-JARs. Dabei handelt es sich um JARs, die für unterschiedliche Java-Versionen vorgesehen sind.
Ab Java 9 verfügen der Java-Compiler (javac) und das Java Archive Tool (jar) über einen neuen Aufrufparameter -release. Damit generiert der Java-Compiler von Version 9 Bytecode für die Zielplattformen Java 6 bis Java 9.
Dem Java Archive Tool teilt man über die --release-Option darüber hinaus auch mit, in welches Verzeichnis das Multi-Release-JAR unterhalb META-INF/versions
jeweils nach Versionen gruppiert gespeichert werden soll.
In dem durch die Option -output genannten Verzeichnis legt der Linker die Unterverzeichnisse: bin, conf und lib an und erstellt zusätzlich eine Datei mit dem Namen release. Die release-Datei enthält systemrelevante Informationen wie den Zeitpunkt des Linkvorgangs oder die Versionsnummer des Betriebssystems und des JDK.
Ergänzend besitzt der Linker eine Plug-in-Architektur, damit Werkzeughersteller den Link-Prozess an ihre eigenen Bedürfnisse anpassen können. Mittels -plugins-module-path teilt man dem Linker mit, in welchem Pfad er eventuell benötigte Plug-ins findet.

JShell – die interaktive Kommandozentrale für Java

Einige Programmiersprachen besitzen im Umfeld ihrer Entwicklungsumgebung ein Werkzeug, häufig Shell genannt, um direkt Befehle der Sprache auszuführen. Derartige Werkzeuge sind unter dem Akronym REPL (Read-Evaluate-Print Loop) schon länger bekannt. Als Programmierer übergibt man der Shell Sprachbefehle, diese führt die Befehle aus und zeigt die Ergebnisse unmittelbar am Bildschirm an. Mit ­Java 9 steht für Entwickler auch eine derartige Shell zur Ver­fügung: die JShell. Entwickler arbeiten interaktiv mit der ­JShell; sie ist recht tief mit dem JDK integriert, kennt zum Beispiel auch import-Befehle. Ergänzend besitzt die JShell auch ein eigenes API, um sie von anderen Anwendungen aus nutzen zu können. JShell stellt keine neue Sprache oder eine neue Syntax dar, vielmehr verarbeitet sie reines Java. Sie eignet sich besonders für Anfänger, die Java schnell erlernen wollen, da man nach Eingabe der Befehle von der JShell eine ­sofortige Rückmeldung erhält. Java-lernende Anwender wie Studenten können so mit dem JDK und den Fähigkeiten von Java experimentieren und mit der JShell schrittweise komplexeren Code schreiben. Damit unterstützt die JShell erstmals Prototyping für Java, ohne den sonst üblichen und länger dauernden Edit-Compile-Execute-Zyklus einhalten zu müssen.
Die Nutzung der JShell setzt eine Installation des JDK 9 voraus. Ihre Befehlszeile erreicht man über die Eingabe des Kommandos jshell auf der Betriebssystemebene (Bild 9).
Java 9 besitzt eine interaktive Kommandozeile, um Java-Befehle zu verarbeiten und deren Ergebnisse anzuzeigen (Bild 9)
Bereits von der JShell verarbeitete Befehle legt sie in einem History-Speicher ab, zusätzlich besitzt sie Editier- und Tabulator-Funktionen. Generell kennt die JShell zwei verschiedene Befehlstypen:
  • Java-Anweisungen: Dabei handelt es sich um Java-Quellcode, also Java-Befehle, woraus im Allgemeinen eine Deklaration oder eine Ausführung von Befehlen resultiert. Zu ihnen gehören auch alle über das JDK verfügbaren Funktionen, extern zugängliche Packages sowie die Verarbeitung ganzer Java-Ausdrücke. Nicht erlaubt sind derzeit Befehle, welche die externe Umgebung modifizieren, wie die Definition von Packages oder die Verwendung von public private … oder static final.
  • JShell-Kommondos: Sie dienen zur Steuerung des Tools oder um Informationen über das Tool zu erhalten. Um JShell-Kommandos von Java-Anweisungen zu unterscheiden, beginnen diese immer mit einem Slash-Zeichen /. JShell-Kommandos bestehen aus fünf Befehlsgruppen: zur Information wie /list, /vars oder /types, der Befehlskontrolle wie /edit, /save oder /open, Session-Befehlen wie /exit oder /reload, History-Befehlen wie /history oder /-n und allgemeinen Befehlen wie /set oder /help.
Für ein produktiveres Arbeiten mit der JShell benötigt man bestimmtes Wissen seitens ihrer Implementierung. Konzep­tionell basiert das Editieren der Zeilen in der JShell auf der Open-Source-Bibliothek JLine. Dabei handelt es sich um eine Java-Bibliothek, die Eingaben von einer Konsole des Betriebssystems verarbeitet und damit verbundene Java-Befehle ausführt. Die Realisierung von JLine besitzt keinerlei weitere Abhängigkeiten zu anderen Bibliotheken.
Die Funktionalität von JLine bildet die Fähigkeiten der Shells moderner Betriebssysteme wie bash oder tcsh ab. Auf diese griff das OpenJDK-Projekt zurück, um dem Programmierer mit der JShell eine interaktive Java-Shell bereitzu­stellen.
Spezielle Cursor-Steuerungen sind für die Navigation ­innerhalb der Befehlshistorie sowie für die Autovervollständigung von Java- oder Shell-Kommandos vorgesehen. So führt man mit der Return-Taste eingegebene Befehle aus oder holt diese aus dem History-Speicher, um sie nochmals auszuführen.

Cursor-Steuerungstasten

Mit den Cursor-Steuerungstasten bewegt man den I-Beamer innerhalb der Eingabezeile oder scrollt mit ihnen durch die im History-Speicher abgelegten Befehle.
Die Tabulatortaste hilft bei der automatischen Vervollständigung einer aktuell in der Kommandozeile stehenden Zeichenkette. Mit dem /list-Kommando zeigt man sich die aktuell im Speicher der JShell befindlichen Befehle an. Über /save <dateiname> speichert man diese Befehle in einer externen Datei mit dem übergebenen Namen ab.
Java 9 kennt zwei weitere Modul-Typen
Neben den Modul-Typen zur Gewährleistung der Abwärtskompatibilität (Backward Compatibility) kennt das neue Modulsystem noch zwei weitere Modul-Typen:
  • Plattform-Module (Platform Modules): Dabei handelt es sich um Module, die aufgrund der Modularisierung der Java SE entstanden.
  • Anwendungsmodule (Application Modules): Diese stammen aus der Modularisierung von Bibliotheken und Anwendungen.
Über /open <dateiname> führt die JShell alle in der genannten Datei enthaltenen Befehle unmittelbar aus. Will man sich die im jeweiligen Ordner enthaltenen Dateinamen anzeigen lassen, so genügt die Eingabe eines Anfangsbuchstabens ihres Namens oder einer in ihm enthaltenen Zeichenkette; anschließend zeigt ein Druck auf die Tabulatortaste die dazu passenden Dateien an. Die beiden Befehle /methods und /vars geben gezielt einzelne Methoden beziehungsweise Variablen oder alle der JShell aktuell zugänglichen aus. Die Kenntnis der Zeilensteuerung des Emacs-Editors ist recht hilfreich, da die ­JShell diese unterstützt. Um einen laufenden Prozess oder die Zeilenverarbeitung zu unterbrechen, greift man auf [Strg/Ctrl C] zurück.

Verbesserungen beim Einsatz von Betriebssystemprozessen

Im Zentrum der Spezifikation der Programmiersprache Java stand die Unabhängigkeit von einer bestimmten Plattform. Diese Unabhängigkeit bezog sich vor allem auf das Betriebssystem und andere plattformabhängige Schnittstellen. Die Vergangenheit hat jedoch gezeigt, dass man zur Realisierung spezifischer Anwendungen dennoch innerhalb eines Java-Programms plattformabhängige Programme, die nicht auf ­Java basieren, ausführen oder auf diese zugreifen können muss. Aus diesem Grund besitzt das JDK schon seit Längerem ein Process API.
Modul-Graphen und deren Eigenschaften
Allgemein beschreibt ein Modul-Graph Beziehungen zwischen Modulen (Knoten: Module, Kanten: Beziehung zwischen den beteiligten Modulen). Java 9 unterteilt diesen (gerichteten) Graphen in zwei weitere Typen:
  • Modul-Abhängigkeits/Dependency-Graph: Er bildet alle requires-Beziehungen zwischen Modulen rekursiv ab. Ausgehend von einem Modul als Startknoten dienen die von ihm gelesenen Module als Endknoten. Die Kante zwischen Start- und Endkonten stellt die requires-Beziehung dar. Danach betrachtet man Endknoten als neue Startknoten zur rekursiven Erweiterung des Graphen.
  • Module-Readability Graph: Dieser Graph-Typ entsteht durch Erweiterung des Modul-Abhängigkeitsgraphen um diejenigen Endknoten und Beziehungen, die lesend auf den Endknoten als Modul von einem Startknoten aus zugreifen – obwohl das Start-Modul keine direkte requires-Beziehung zum Endknoten besitzt.
Dieses Process API bildet die Schnittstelle der JVM zum Betriebssystem, denn Java selbst unterstützt kein Prozessmodell, mit dem man verschiedene Prozesse innerhalb derselben virtuellen Maschine parallel behandeln kann. Daher handelt es sich bei diesen seitens der Java-Anwendung verwalteten Prozesse um reine Verarbeitungseinheiten, die innerhalb des Betriebssystems und nicht innerhalb der JVM ablaufen. Ein über die start()-Methode der ProcessBuilder-Klasse erzeugter Prozess läuft daher außerhalb der JVM auf der Ebene des Betriebssystems.
Die Kommunikation zwischen der virtuellen Maschine und dem externen Prozess des Betriebssystems erfolgt über dessen InputStream und OutputStream. Mit Java 9 verbessert sich das Process API, sodass man als Entwickler leichter zwischen Java und dem Betriebssystem Informationen austauschen kann.
In Version 9 kann man mittels des Process API in Java aktuell laufende Prozesse anzeigen lassen und zusätzlich einige ihrer Eigenschaften wie ProcessID, Name und Status abfragen. So ermittelt zum Beispiel der nachfolgende Befehl die dem aktuell laufenden Prozess zugeordnete ProcessID:

System.out.println("Die ProcessID ist " +
Process.getCurrentPid());
Insgesamt gestaltet sich mit Version 9 das Management von Prozessen auf der Ebene des Betriebssystems wesentlich einfacher. Eine wichtige Erleichterung brachte ein neues Interface ProcessHandle und dessen Methoden. So macht dieses die nachfolgende bisher notwendige Aufteilung der betriebssystemseitigen Zeichenketten über das @-Zeichen hinfällig:

Integer.parseInt(processName.split("@")[0]);
Jetzt ermittelt man direkt die gewünschten Informationen wie Startparameter, Benutzer oder Pfad über die Abfrage der gewünschten Felder von ProcessHandle.Info. Dafür stehen die Methoden allProcesses(), arguments(), children(), command(), current(), descendants(), parent() oder user() und weitere zur Verfügung.

HTTP/2-Protokoll für effizientere Netzkommunikation

Die Kommunikation über das Netz gestaltet sich auf der Basis des alten HTTP-Protokolls extrem zeitaufwendig, zumal weder Multiplexing noch Resource Correlation unterstützt werden. Um in der Vergangenheit mit ­Java in Anwendungen performant über ­HTTP kommunizieren zu können, musste man daher häufig auf lästige, codeaufwendige Workarounds zurückgreifen. Zu den gängigen Workarounds zählen CSS-Sprites, Domain/Hostname Sharding, Image Spriting oder Resource Inlining; sie alle besitzen eine wesentlich schlechtere Performance als das neue binäre HTTP/2-Protokoll.
Mit Version 9 bietet Oracle erstmals für die Java SE einen HTTP/2-Support an und ersetzt den bisher gängigen Einsatz des HttpURLConnection-API. Das neue HTTP/2-API lässt sich relativ leicht benutzen und deckt die meisten in der Praxis auftretenden Anwendungsfälle ab. Die Kommunikation über das Netz kann dabei sowohl synchron als auch asynchron erfolgen. Bei der Implementierung achtete man auf Rückwärtskompatibilität, da Java 9 sowohl HTTP 1.1 als auch HTTP/2 unterstützt. In bestehenden HTTP-Anwendungen sind nur die alten HTTP/1.1-Workarounds zu beseitigen.
Die beiden Unix-Shells: bash und tcsh
Als freie Unix-Shell und Teil des GNU-Projekts findet man die bash als Standard-Shell auf vielen Unix-basierten Betriebssystemen. Die bash beherrscht viele Befehle der Bourne-Shell (sh) und die meisten Funktionen der Korn-Shell (ksh). Bei der weitgehend mit der C-Shell (csh) kompatiblen tcsh handelt es sich um die mit BSD-basierten Systemen wie FreeBSD ausgelieferte Root-Shell.
Als sogenannte Root-Shells zielen beide darauf ab, das Arbeiten mit dem Betriebssystem für den Administrator (häufig Superuser genannt) zu erleichtern. Dem Superuser als Benutzerkonto zugeordnet ist das Root-Konto. Dieser Account eignet sich nicht für alltägliche Aufgaben, da er mit umfassenden Rechten versehen ist, sondern ist nur für besondere Verwaltungsaufgaben vorgesehen.
Um diese Aufgaben der Administration und Verwaltung vornehmen zu können, verfügen beide Shells über zahlreiche eingebaute Befehle und weitere Schlüsselwörter für die Programmierung. Damit sind sie in der Lage, Befehle des Betriebssystems direkt auszuführen. Zusätzliche Features wie Befehlszeilenergänzung oder Setzen von Umgebungsvariablen tragen wesentlich zu der Mächtigkeit beider Shells bei.
Im Zentrum der Implementierung stehen der wiederverwendbare HTTP-Client-Container und HttpRequest. Ein ­HttpResponse erfolgt über die response()-Methode mittels Blocking, die Methode responseAsync() arbeitet asynchron; zudem können mehrere Responses asynchron über multiResponseAsync erzeugt werden. Innerhalb eines Response lassen sich HTML-Bodies mit einem BodyProcessor behandeln. Standardmäßig verarbeitet dieser in Java 9 die Datentypen Array, String und File; weitere eigene Datentypen definiert man selbst.
Der in Java 9 integrierte MultiProcessor verfügt über die Interface-Methoden onStart und onComplete, um auf Standard­ereignisse zu reagieren. Über die multiFile()-Methode von HttpResponse erhält man einen MultiProcessor für Files, der Bodies für Dateien schreibt und eine Map<URI,Path> zurückliefert. Damit lassen sich ganze Bündel von Dateien asynchron verarbeiten. Diese mit HTTP/2 verbundenen Features nahm Oracle als Grundlage in die Servlet-4-Spezifikation der neuen, auf Java 9 basierenden Java-EE-Version auf.

Vereinfachungen für Collections

Datensammlungen (Collections) stehen schon lange in Java zur Verfügung und wurden kontinuierlich über die verschiedenen Versionen hinweg weiterentwickelt. Man denke nur an das Collection-Framework, das java.util.concurrent-Package oder die mit Java 8 eingeführten Verbesserungen wie das Iterable-Interface (forEach, removeIf, replaceAll) und Ähnliches. Version 9 liefert jetzt sogar fertige Factory-Methoden für Collections: List.of(), Set.of(), Map.of(). Mit ihnen kann man Listen, Mengen oder Maps in fixer Länge (bis zu 10 Elemente), aber auch selbst programmiert in variabler Länge erzeugen:

// Liste mit drei String-Elementen
List<String> stringList = List.of("Otto", "Karl", "Bernd");

// Menge mit vier Integer-Elementen
Set<Integer> intSet = Set.of(1, 2, 3, 4);

// Map mit drei Eintraegen
Map<String,Integer> stringMap = Map.of("a",1, "b",2, "c",3);
Das bisherige, leider seit Jahren wenig veränderte, java.util.logging-API des JDK erforderte einen sehr großen Aufwand für die Programmierung von Logs und deren Ausgabe. Daher griffen viele Entwickler in der Vergangenheit auf spezielle Logging-Frameworks (Log4j, Logback, SLF4J) zurück. Mit Version 9 existiert jetzt ein minimales Logging-API im JDK mit einem speziellen Service-Interface, das die Aufzeichnung von Logs wesentlich vereinfacht. Zusätzlich kann dieses Service-Interface Logging-Nachrichten an ein externes Logging-Framework weiterreichen, um auch dieses in der Entwicklung zu nutzen (Redirected Platform Logging).

Reactive-Streams-Spezifikation

Java 9 enthält eine Implementierung der Reactive-Streams-Spezifikation und schafft so eine Basis für die ersten Schritte in Richtung reaktiver Programmierung in Java. Die damit verbundenen Prinzipien sollen die Zusammenarbeit zwischen den bereits für Java verfügbaren Bibliotheken und Frameworks der reaktiven Programmierung für das JDK ebnen. Die Neuerungen der Reactive Streams basieren auf dem Publish-Subscribe-Pattern. Nach der Anlage eines Publishers und eines Subscribers kommunizieren beide direkt asynchron über eine zuvor anzulegende Subscription-Beziehung. Damit besitzt das JDK 9 ein Publish-Subscribe-Framework; zudem ist über den JSR ein Technology Compatibility Kit (TCK) für Dritthersteller verfügbar.
Der Prozess-Begriff in der Informatik
In der Informatik stellt ein Prozess die Abstraktion einer Verarbeitungseinheit seitens des Betriebssystems dar.
Das Betriebssystem unterteilt diese Verarbeitungseinheiten mittels einer Hierarchie in mehrere aufeinander abgestimmte Arbeitsschritte, die wiederum Verarbeitungseinheiten bilden. Als nebenläufige Ausführungseinheiten nennt man diese ­Threads. Auf der obersten Hierarchieebene entspricht eine Verarbeitungseinheit einem Programm.
Ein Prozess existiert immer nur zur Laufzeit des Programms und nimmt während seiner Ausführung verschiedene Zustandsinformationen ein.
Die Kontrolle des Prozesses, das heißt, seine Ausführung und dergleichen, erfolgt immer durch das Betriebssystem.
Das Betriebssystem stellt dem Prozess erforderliche Ressourcen in einer Ablaufumgebung zur Verfügung.
Bereits Sun lieferte seit Version 2 als Bestandteil des JDK das Dokumentationswerkzeug Javadoc aus. Damit dokumentieren Entwickler über Kommentare und Tags ihre Programme direkt im Java-Quellcode. Javadoc erzeugt aus diesen die seitens des JDK bekannte API-Dokumentation als Standardausgabe im HTML-Format. Mit Version 9 unterstützt Javadoc jetzt zusätzlich zum bisherigen HTML 4.01 über den -html5-Parameter eine HTML5-Ausgabe. Ferner prüft das -Xdoclint-Feature, ob sich innerhalb der Javadoc-Kommentare Fehler befinden. Eine neue Suchfunktion für die generierte API-Dokumentation findet direkt Module, Packages, Typen und Member.

Zentrale Verbesserungen für die Sprache

Java 8 brachte Default-Methoden für Interfaces in die Diskussion, um diese flexibler gestalten und einfacher weiterentwickeln zu können. Leider führte dieser Ansatz für die Erweiterbarkeit von Interfaces über Methoden dazu, dass man sie im Fall einer Wiederverwendung als public deklarieren oder ihre Implementierung in eigene Helper-Klassen auslagern musste.
Java 9 beseitigt nun diese Schwäche: Methoden in einem Interface können als private (static) deklariert werden:

public interface Api {
// private Methoden
  private int pruefen (int data) {
    System.out.println("Eingabe prüfen: ");
    return true;
  }
}
Java 7 schaffte den häufig fehleranfälligen finally-Block für die try-Anweisung ab; das Schließen der beteiligten Ressourcen (Reader, Sockets, Streams) erfolgte ab Version 7 am Ende des try-Blocks automatisch. In Java 9 erlaubt die Try-With-Resources-Anweisung den Einsatz von Variablen, solange diese als (effective) final anzusehen sind. Damit kann diese Variable direkt dem try-Konstrukt übergeben werden:

// Fuer Konstante als Ressource
// keine weitere Behandlung erforderlich
try (resource) {
  resource.doSomething();
}
Anonyme Klassen stellen einen Spezialfall einer lokalen Klasse dar: Sie besitzen keinen eigenen Namen und werden stets innerhalb einer new-Anweisung definiert. Damit existiert eine anonyme Klasse immer nur im Kontext einer bestimmten Klasse.
Da sie keinen Namen besitzt, beansprucht eine anonyme Klasse auch keinerlei Ressourcen im Namensraum der Klassen. Im Unterschied dazu erbt eine anonyme Klasse jedoch von anderen gemäß der Klassenhierarchie. Java 9 macht den mit Version 7 eingeführten Diamant-Operator (Paar spitzer Klammern <>) für anonyme Klassen zugänglich:

NeueKlasse<?> neukla = new NeueKlasse<>(1) {
  // Anonyme innere Klasse
};
Die Anwendung des Diamant-Operators für eine anonyme Klasse setzt voraus, dass der Datentyp dem Compiler bekannt oder zumindest aus dem Kontext ableitbar ist. Ist der Typ nicht bekannt, ist er also auch nicht aus dem Kontext der Anweisungen ableitbar, muss der Typ explizit bei der new-Anweisung mit angegeben werden. Diese Notwendigkeit resultiert aus dem Sachverhalt, dass zur Ausführung des Codes letztlich die JVM den passenden Typ kennen muss.
Jeder GC hat spezifische Vor- und Nachteile
Die vier gängigen Garbage Collectors (GC) arbeiten alle gene­rationsbasiert; sie teilen den Heap (Speicherbereich für Java-Objekte) in zwei verschiedene, auf die Lebensdauer der Objekte ausgerichtete Segmente (Young- versus Old-Generation). Während kurzlebige Objekte über den Young-Generation Heap angelegt und baldmöglichst dem GC zugeführt werden, wandern langlebige oder permanente Objekte in den Old-Generation Heap:
  • Serial-Collector: Er eignet sich nur für kleine Heap-Größen und ist auf Single-Threaded-Umgebungen ausgerichtet; kommt deshalb fast nie zum Einsatz.
  • Parallel / Throughput-Collector: Er war bisher der Standard-GC, unterstützt mehrere Threads und einen kompakten Heap. Nachteilig wirkte sich der Ausführungsstopp von ­Threads aus, wenn die JVM nur kleine oder vollständige Speicherbereinigungen durchführt.
  • CMS-Collector: Als Algorithmus basiert dieses GC auf parallelen Scans durch den Heap; unbenutzte Objekte werden als recyclebar markiert. Dieser GC arbeitet besonders gut bei der Verfügbarkeit mehrerer CPUs und einer Heap-Größe kleiner als 4 GByte.
  • G1-Collector: Wurde speziell für Heaps größer als 4 GByte entwickelt. Er basiert auf mehreren Threads, die im Hintergrund Scans durch den Heap ausführen und so die Größe des Heap dynamisch verwalten.
Bei der Ausführung einer Java-Anwendung übernimmt die JVM schon immer das Speichermanagement. Im Unterschied zu C müssen Java-Programmierer den eigenen Speicher nicht selbst verwalten. Ein im Hintergrund laufender sogenannter Garbage Collector (GC) gibt nicht mehr benötigten Speicherplatz wieder frei. Als Entwickler kann man allerdings Einfluss auf die Auswahl der vom Garbage Collector einzusetzenden Algorithmen nehmen. In der Regel legt man diese über die Start-Flags der JVM für den gewünschten GC fest.
Eine falsche Wahl des GC-Algorithmus hatte immer drastische Auswirkungen auf die Performance einer Anwendung. Um dies den Entwicklern bewusst zu machen, hat Oracle bereits mit Java 8 einige dieser Start-Flags als deprecated erklärt (Bild 10).
Einige der Aufrufparameter für das Garbage-Collection der JVM stehen ab Java 9 nicht mehr zur Verfügung (Bild 10)
Mit Java 9 erhält man als Entwickler nun keinen Zugriff mehr auf diese, zumal sie auch zunehmend seltener benutzt wurden. Die Umsetzung dieser deprecated GC-Kombinationen erwies sich aufgrund neuer Sprachfeatures auch als recht komplex. Oracle realisierte den G1-Collector bereits mit Java 7 und liefert ihn seit 2012 mit dem Update 4 Java7u4 im JDK aus. G1 arbeitet parallel, mit kleinen, kurzen und vorhersagbaren Pausen beim GC; zudem minimiert G1 die Pausen innerhalb des GC. Insbesondere gelang es, mit Version 9 die Performance des G1 in zentralen Bereichen (Strings, Class-Unloading, Collections) zu verbessern. So speichert G1 mehrfach vorhandene Zeichenketten als String nur einmal ab. Klassen ohne aktive Objekte lagert G1 automatisch aus; dies kann aber über das Start-Flag -XX:+Class­Un­loadingWithConcurrentMark ausgeschaltet werden.
Deklaration: final und effective final
Java kennt lediglich die final-Deklaration für Variablen/Parameter. Bei einer final-Deklaration handelt es sich um eine Konstante, die per Definition programmseitig unveränderbar ist. Enthält ein Programm eine Variable oder einen Parameter, die oder der zwar nicht als final deklariert ist, aber sich wie eine Konstante verhält, also seitens des Programms nicht verändert wird, so bezeichnet man diese Variable oder diesen Parameter als effective final. Die Entscheidung, ob eine Variable oder ein Parameter als effective final anzusehen ist, kann bereits während der Übersetzung des Programms getroffen werden.
Die Verwaltung von Collections erfolgt über G1 wesentlich effizienter. Darum und wegen weiterer Vorteile erklärte Oracle G1 zum neuen, standardmäßig eingestellten GC für ­Java 9. Auch bedeutende Java-Frameworks und Anwendungen aus den Bereichen GUI, Online Analytical Processing (OLAP), In-Memory-Computing und Data-Analytics-Plattformen setzen seit Java 8 wegen der schnelleren Laufzeiten G1 ein. Andere Hersteller einer JVM wie Google oder Azul Systems preisen jedoch ihren GC als besser an. Daher sollte im konkreten Umfeld einer Anwendung unter Berücksichtigung von Infrastruktur und Benutzungsszenarien eine individuelle Entscheidung für die Auswahl des GC getroffen werden.
Links zum Thema



Das könnte Sie auch interessieren