Unser Newsletter rund um technische Themen,
das Unternehmen und eine Karriere bei uns.

6 Minuten Lesezeit (1115 Worte)

Java 9 macht schlank, kann aber noch mehr? - Neuheiten Java 9 (Teil II)

Im ersten Teil kam das Hauptthema von Java 9, die Modularisierung (Codewort Jigsaw), in Grundzügen zur Sprache. Wir wollen nun einen erweiterten Blick auf dieses Feature von Java 9 nehmen, indem wir Auswirkungen der Modularisierung betrachten. Eine wichtige stellt die Möglichkeit dar, wesentlich schlankere Anwendungspakete schnüren zu können. Aber auch weitere bedeutende Neuerungen sollen hier in den Fokus genommen werden.

JIGSAW – DIE AUSWIRKUNGEN?

Modularisierung in Java 9 arbeitet – welch Wunder – mit Modulen, das haben wir in Teil I schon ausgearbeitet. Module ermöglichen eine zusätzliche Gruppierung von Java-Strukturen oberhalb von public. Die Beschreibung findet sich in einer speziellen, modul-spezifischen Datei namens modul-info.java. Darin steht der Name/ID des Moduls, die Abhängigkeiten des Modul zu anderen Modulen und die eigenen, für andere nutzbaren APIs.

Das speziell Neue bei Modulen ist die Fortführung der Abkapselung auf JAR-Ebene. Das heißt, anders als bei früheren Java Versionen gibt es es für nicht authorisierte Stellen keine Möglichkeit, auf Modulinhalte in JAR-Dateien zuzugreifen. Das unterscheidet sich fundamental zum alten Reflection-Mechanismus, mit dessen Hilfe es möglich war, auch auf die innersten Strukturen der Java Runtime Zugriff zu erlangen. Eine gewisse Berühmtheit hat dabei die Klasse sun.misc.Unsafe erlangt, in der spezifische, betriebssystemnahe Methoden untergebracht sind. Mittels Reflection konnte man auch darauf zugreifen und sich in den Innereien des Betriebssystems austoben – mit unabsehbaren Gefahren für die Sicherheit und die Abwehrmöglichkeit gegen Schadsoftware, Viren und feindliche Attacken.

Gerade diese spezielle Klasse hat allerdings immense Bedeutung für viele Unternehmen, die Frameworks und Tools basierend auf Java anbieten, wie z.B. Hadoop, Cassandra, Spring, um nur einige zu nennen.

Abb. 1: Grundsätzliche Darstellung der Hierarchiestufen im Java-System. Mit Modulbildung ist eine zusätzliche Gruppierung und Kapselung von Paketen möglich.

MODULARISIERUNG BRINGT PERFORMANCE-GEWINNE – WIE DAS?

Ein weiterer positiver Effekt der Modularisierung ergibt sich durch die Möglichkeit der exakten Schneidung einer Java-Anwendung. Bei Nutzung der Modultechnik ist es unabdingbar, genau zu spzifizieren, was benötigt und was zur Verfügung gestellt wird (siehe modul-info.java).

Daraus folgt aber auch, dass nicht mehr gebraucht wird als nötig, d.h. der Compiler und Linker kann zur Anwendung genau das – und nur das – packen, was zum reibungslosen Funktionieren erforderlich ist. Das kann zu wesentlich kleineren Installationspaketen und Runtime Images führen, was wiederum Lade- und Ausführungszeiten in der JVM senken kann. Die Mindestgröße früherer Anwendungen (<= Java 8) wurde bestimmt von der Größe der Runtime-Bibliothek rt.jar, welche praktisch die gesamte Java API beinhaltete und immer mitsamt der Anwendung installiert sein musste (zum Umfang der rt.jar siehe Abbildung 2).

Performance-Gewinne ergeben sich durch schnelleres Laden von Klassen und viel unkompliziertere Handhabung des Classpath bzw. Modulpath. Der Classpath musste früher aufwendig durchsucht werden und es konnte leicht zu Konflikten führen, wenn Klassen mehrfach auftraten.

1 META-INF/
2 META-INF/MANIFEST.MF
3 com/oracle/net/Sdp$1.class
4 com/oracle/net/Sdp$SdpSocket.class
5 com/oracle/net/Sdp.class
6 com/oracle/nio/BufferSecrets.class
7 com/oracle/nio/BufferSecretsPermission.class
8 com/oracle/util/Checksums.class
9 com/oracle/webservices/internal/api/EnvelopeStyle$Style.class
10 com/oracle/webservices/internal/api/EnvelopeStyle.class
11 com/oracle/webservices/internal/api/EnvelopeStyleFeature.class
12 com/oracle/webservices/internal/api/databinding/
Databinding$Builder.class
...
19728 java/lang/System.class
19729 java/lang/ClassLoader.class
19730 java/lang/Cloneable.class
19731 java/lang/reflect/Type.class
19732 java/lang/reflect/AnnotatedElement.class
19733 java/lang/reflect/GenericDeclaration.class
19734 java/lang/Class.class
19735 java/lang/CharSequence.class
19736 java/lang/Comparable.class
19737 java/io/Serializable.class
19738 java/lang/String.class
19739 java/lang/Object.class 

Abb. 2: Ein kleiner Auszug der rt.jar aus JRE 8, der Anfang und das Ende. Es beinhaltet die gesamte Java-API mit insgesamt ca. 19700 class-Dateien, was zur Gesamtgröße von ca. 52 MB führt.

MIT JLINK ZURÜCK ZU SCHLANKER FIGUR

Mit jlink hat Java 9 genau das Werkzeug bekommen, um zielgenaue Laufzeit-Images erstellen zu können. Was ist damit gemeint? Stellen wir uns vor, eine Java-Anwendung soll mit minimalem Speicher auf der Platte auskommen.

Bisher war es notwendig, dass wenigstens eine vollwertige JRE vorliegt. Mit dem Modulkonzept und jlink können wir nun ein maßgeschneidertes Paket zusammenstellen. In solche Images gelangen nur genau die Module und Konstrukte, die für den Betrieb der Anwendung benötigt werden. Das stellt eine Abkehr von der alten Java-Strategie dar, nach der nahezu alle Strukturen der Java-Laufzeitumgebung in der rt.jar (Runtime Jar) untergebracht waren.

Die Aufrufsyntax von jlink ist fast selbsterklärend:

jlink [jlink Optionen]
–-module-path <Modulpfad>
--add-modules <Liste von Root-Modulen>
--output <Verzeichnisname> 

modul-path bezeichnet den Pfad zu „unseren" Modulen, also zu den applikationsspezifischen Programmanteilen. Es ist erstaunlich, dass das ausreichen soll, aber durch die explizite Angabe von Abhängigkeiten mittels exports und requires kann das System exakt ermitteln, welche Module insgesamt notwendig sind für einen reibungslosen Programmablauf.

Diese Möglichkeit der Verschlankung kommt dem aktuellen Trend der Verkleinerung entgegen. Neben den immer größer und leistungsfähiger werdenden Serversystemen gibt es zahllose technische IT-Einheiten mit begrenztem Anforderungsprofil, die immer kleiner werden. Zu nennen wäre da beispielhaft Sensortechnik im Maschinenbau, wo es z.B. lediglich um genaue Temperaturmessungen und ggf. daraus abzuleitende Verfahrensschritte gehen könnte. Für solche Aufgabenstellungen kommen schmalbrüstig definierte Kleinstcomputer zum Einsatz, für die die absolute Größe eines Laufzeitimages ein entscheidendes Kriterium sein können.

EIN WEITERE STREAM-API: TAKEWHILE, DROPWHILE

Mit Java 8 kamen die coolen Streams, die mit den gleichzeitig eingeführten Lambdas ein mächtiges Programmierparadigma anbieten. Die Stream-API ist die Java-Anwort auf immer größer werdende Datenmengen und auf das Thema Big Data. Anstatt beispielsweise eine große Datenstruktur (> 10 Mio. Datenelemente) in eine Java-Collection zu packen und sie als Ganzes zu handhaben, kann man einen Stream definieren. Der geht elementweise vor, nimmt sich das erste Element vor, dann das zweite usw. Das erlaubt den effizienten Umgang mit riesigen und mit potenziell unendlichen Datenstrukturen (Beispiel: alle positiven, ganzen Zahlen).

Java 9 hat einige neue Methoden zur Stream API im Gepäck, als da wären:

1. Stream::takeWhile()
2. Stream::dropWhile()
3. Stream::ofNullable()
4. Stream::iterate()( 

Die ersten beiden sind antagonistisch zueinander, beide verarbeiten nur einen Teil eines Streams, wobei takeWhile() genau die nimmt, die dropWhile() weglässt. Wir sehen uns das an einem Beispiel genauer an, es sollte dadurch offensichtlich werden (siehe Abbildung 3).

Stream.of(2, 4, 6, 8, 9, 10, 12).takeWhile(n -> n % 2 == 0).forEach(System.out::println);// Ausgabe:
// 2
// 4
// 6
// 8 
Stream.of(2, 4, 6, 8, 9, 10, 12).dropWhile(n -> n % 2 == 0).forEach(System.out::println);// Ausgabe:
// 9
// 10
// 12 

Abb. 3: Methode Stream::takeWhile(), um Elemente solange zu adressieren, bis eine Bedingung erfüllt ist (hier: n % 2 == 0, also die ersten Zahlen, die gerade sind). Auch zu sehen ist die antagonistische Methode Stream::dropWhile().

Stream.iterate(1, i -> 2 * i).forEach(System.out::println); // Ausgabe: 1 2 4 8 ... 
Stream.iterate(1, i -> i <= 10, i -> 2 * i).forEach(System.out::println); // Ausgabe: 1 2 4 8 

Abb. 4: Stream::iterate() erlaubt nun die begrenzte Erzeugung vonStream-Inhalten. Dazu ist eine überschriebene Methode mit drittem Argument hinzugekommen. Dieses Argument stellt eine Abbruchbedingung dar, ähnlichwie einer klassischen For-Loop.

FAZIT

Mit der Modularisierung bekommen wir ein mächtiges Werkzeug an die Hand, um Java-Anwendungen sicherer, kompakter und performanter gestalten zu können.

In diesem Artikel lag der Schwerpunkt auf der Kompaktheit, die durch den Wegfall der rt.jar erreicht wird.

Selbstverständlich gibt es dazu eine Seminarveranstaltung „Java-Neuheiten". Dort lernen Sie als Seminarteilnehmer die wesentlichen Neuerungen zu Java 8, 9 und 10 kennen.

Dr. Hubert Austermeier (info@ordix.de)

Senior Chief Consultant bei ORDIX

 

Kommentare

Derzeit gibt es keine Kommentare. Schreibe den ersten Kommentar!
Donnerstag, 26. Dezember 2024

Sicherheitscode (Captcha)

×
Informiert bleiben!

Bei Updates im Blog, informieren wir per E-Mail.

Weitere Artikel in der Kategorie