Teil 2: Nur das, was wir wirklich brauchen

Java 9 Tutorial: Erstellen von modularen Laufzeit-Images mit jlink

Alexandru Jecan

© Shutterstock /Aquir

Java 9 stellt ein neues Werkzeug für die dynamische Verknüpfung von Modulen vor: jlink. Es hat die Aufgabe, eine Gruppe von Modulen zusammenzubauen, um dadurch ein Laufzeit-Image zu erzeugen. In diesem Tutorial zeigen wir, wie man dabei vorgehen muss.

Java 9 Tutorial – Teil 2

Im zweiten Teil unseres Java 9 Tutorials beschäftigen wir uns mit jlink, einem der zentralen Tools in Java 9. In welchem Kontext ist der Einsatz von jlink sinnvoll?

Während des Softwareentwicklungsprozesses begegnen wir verschiedenen Situationen, in denen wir ein kundenspezifisches Java Runtime Environment (JRE) für unser Betriebssystem benötigen. Die Gründe dafür sind unterschiedlich: Vielleicht wollen wir eine bessere Leistung erzielen oder haben einige kundenspezifische Bibliotheken, die nur mit einem bestimmten Betriebssystem funktionieren. jlink hilft uns, diese Probleme zu lösen, indem wir mit seiner Hilfe eine benutzerdefinierte Version vom JRE erstellen können, die maßgeschneidert auf unser Betriebssystem ist und nur die Module enthält, die wir wirklich benötigen.

Java 9 Tutorial – die Serie

 

Erstellen von modularen Laufzeit-Images mit jlink

Java 9 stellt ein neues Werkzeug für die dynamische Verknüpfung von Modulen vor: jlink. Es hat die Aufgabe, eine Gruppe von Modulen zusammenzubauen, um dadurch ein Laufzeit-Image zu erzeugen. Um jlink zu verwenden, sind keine Änderungen im Quellcode erforderlich. jlink stellt in einem maßgeschneiderten Laufzeit-Image die Module zusammen. Es ändert die module-info.class-Dateien nicht. jlink startet mit den Modulen, die wir angeben, anschließend sucht es rekursiv nach allen requires-Anweisungen innerhalb der Deskriptoren der angegebenen Module. Ein Laufzeit-Image, das von jlink erzeugt wurde, enthält die minimale Anzahl der benötigten Module zusammen mit all ihren Abhängigkeiten. Während des Baus des Laufzeit-Images sind wir in der Lage, genau zu spezifizieren, welche Module dem Laufzeitbild hinzugefügt werden sollen. Als Ergebnis wird eine plattformspezifische, binäre, ausführbare Datei erstellt.

Das jlink-Werkzeug führt den Auflösungsprozess aus, um die für die Erzeugung des Laufzeit-Images erforderlichen Module zu berechnen. Das minimale Laufzeit-Image, das wir hypothetisch mit jlink erzeugen können, ist ein Laufzeit-Image, das nur das Hauptmodul java.base enthält. Basierend auf der Art der Plattformmodule erkennt jlink, welche die Zielplattform ist. Wenn wir beispielsweise ein Linux-Betriebssystem haben und einen Modulpfad an ein Windows-System weitergeben, wird jlink die Windows-Versionen dieser Module verknüpfen und ein Windows-Laufzeit-Image erzeugen. Es ist wichtig zu wissen, dass der Modulpfad, den wir jlink angeben, das Modul für die Zielplattform sein muss.

Die jlink-Images sind für jedes Betriebssystem spezifisch und sind eine Anpassung des JDKs und der JRE. Ein jlink-Image enthält die Verzeichnisse bin, conf, include, legal, lib und release. Für eine Anwendung, die nur das Modul java.base verwendet, wird ein maßgeschneidertes Laufzeit-Image erzeugt, das nur aus unseren Applikationsmodulen und aus dem java.base-Modul besteht.

Eingabeformate für jlink
Die Eingabeformate, die jlink unterstützt, sind: modulare JAR-Dateien, JMOD- Dateien, JAR-Dateien oder .class-Dateien. Die Ausgabeformate, die jlink generien kann, sind: JAR-Dateien, JMOD-Dateien, JVM-Images und maßgeschneiderte JRE-Images.

jlinks allgemeine Syntax

Um ein Laufzeit-Image mit jlink zu erstellen, wird der jlink-Befehl zusammen mit den notwendigen Optionen ausgeführt. Abbildung 1 zeigt die Syntax des jlink-Befehls zusammen mit den wichtigsten Optionen:

 

Java 9 Tutorial - jlink

Abb. 1: Syntax des jlink-Befehls

 

  • Die Option [jlink Optionen] spezifiziert Optionen, die durch Leerzeichen getrennt sind.
  • Die Option –module-path gibt an, wo sich die Module befinden, die von jlink entdeckt werden sollen. Folgende Formate können im Modulpfad enthalten sein: expandierte Module, modulare JAR-Dateien oder JMOD-Dateien.
  • Die Option –add-modules gibt die Namen der Module an, die zum Laufzeit-Image hinzugefügt werden sollen. Die angegebenen Module werden zum Laufzeit-Image zusammen mit ihren transitiven Abhängigkeiten hinzugefügt.
  • Die Option –output gibt das Verzeichnis an, in dem das maßgeschneiderte Laufzeit-Image erzeugt wird.

jlink ist nicht auf die im vorigen Abschnitt vorgestellten Optionen beschränkt. Tabelle 1 zeigt die wichtigsten Optionen, die mit dem jlink-Befehl verwendet werden können.

Name der Option Beschreibung
–help Druckt alle Optionen
–module-path <Modulpfad > Definiert den Modulpfad
–add-modules <Name des Modul> Spezifiziert die Root-Module, die aufgelöst werden
–output <Name des Verzeichnisses> Spezifiziert den Namen des Verzeichnisses, in dem das Laufzeit-Image erzeugt wird
–limit-modules <Liste der Modulnamen> Begrenzt die Gruppe der Module
–version Zeigt die Versionsinformationen an
–save-opts <Name der Datei> Speichert die jlink-Optionen in der angegebenen Datei
–strip-debug Streift die Debug-Informationen
–disable-plugin <Name des Plug-ins> Deaktiviert das Plug-in
–list-plugins Listet alle verfügbaren jlink-Plug-ins, die über die Kommandozeile erreichbar sind
–ignore-signing-information Überwindet einen Fehler, wenn signierte modulare JAR-Dateien mit dem Laufzeit-Image verknüpft sind
@<Name der Datei> Liest alle Optionen aus der als Argument angegebenen Datei

Tabelle 1: Optionen, die mit dem jlink-Befehl verwendet werden können

Laufzeit-Image mit Linking erzeugen

Linking ist eine neue Phase in der Softwareentwicklung, die optional ist. Diese Phase kann in Java 9 zum Einsatz kommen, falls wir eine modulare Anwendung haben. Ihre Aufgabe ist, ein Laufzeit-Image zu erzeugen, in dem eine Sammlung von Modulen zusammen mit den transitiven Abhängigkeiten zusammengestellt wird. Gemäß OpenJDK erlaubt das Linking künftig viele Optimierungen, die zur Kompilierungszeit oder Laufzeit fast unmöglich sind. Das Ergebnis des Linkers ist plattformunabhängig. Der Linker kann zwei oder mehr Ziele verknüpfen. Wenn Sie beispielsweise das Betriebssystem A verwenden, können Sie auf das Betriebssystem B erfolgreich zielen, vorausgesetzt, dass Ihr Modulpfad die Module für das Betriebssystem B besitzt.

Das Modul jdk.jlink

Abbildung 2 zeigt den Modulgraphen, der das jdk.jlink-Modul mit seinen Abhängigkeiten enthält.

Java 9 Tutorial - jlink

Abb. 2: Modulgraph, der das jdk.jlink-Modul mit Abhängigkeiten enthält

Das Modul jdk.jlink enthält verschiedene Java-Klassen im tools-Verzeichnis. Es gibt ein jimage-Verzeichnis, ein jlink-Verzeichnis und ein jmod-Verzeichnis innerhalb des Tools-Verzeichnisses. Das Modul jdk.jlink hat Abhängigkeiten auf das jdk.internal.opt-Modul und auf das jdk.jdeps-Modul, wie in seinem Deskriptor module-info.java ausgedrückt wird. Das Modul jdk.jdeps hat eine Abhängigkeit zum Modul jdk.compiler. Das Modul jdk.compiler hat eine Abhängigkeit zum Modul java.compiler. Dieses letzte Modul benötigt wiederum das Modul java.logging, und das Modul java.logging benötigt das Modul java.base.

In Abbildung 2 haben wir die Nicht-Standard-JDK-Module (jdk.jlink, jdk.internal.opt, jdk.jdeps und jdk.compiler) grün und die Standard-JDK-Module (java.compiler, java.logging und java.base) blau dargestellt. Die durchgezogenen Linien symbolisieren eine implizierte Lesbarkeit. Die gestrichelten Linien stellen eine einfache Lesbarkeit zwischen den Modulen dar. Der Deskriptor des Moduls jdk.jlink wird in Listing 1 vorgestellt.

module jdk.jlink {
  requires jdk.internal.opt;
  requires jdk.jdeps;

  uses jdk.tools.jlink.plugin.Plugin;

  provides java.util.spi.ToolProvider with
    jdk.tools.jmod.Main.JmodToolProvider,
    jdk.tools.jlink.internal.Main.JlinkToolProvider;

  provides jdk.tools.jlink.plugin.Plugin with
    jdk.tools.jlink.internal.plugins.StripDebugPlugin,
    jdk.tools.jlink.internal.plugins.ExcludePlugin,
    jdk.tools.jlink.internal.plugins.ExcludeFilesPlugin,
    jdk.tools.jlink.internal.plugins.ExcludeJmodSectionPlugin,
    jdk.tools.jlink.internal.plugins.LegalNoticeFilePlugin,
    jdk.tools.jlink.internal.plugins.SystemModulesPlugin,
    jdk.tools.jlink.internal.plugins.StripNativeCommandsPlugin,
    jdk.tools.jlink.internal.plugins.OrderResourcesPlugin,
    jdk.tools.jlink.internal.plugins.DefaultCompressPlugin,
    jdk.tools.jlink.internal.plugins.ExcludeVMPlugin,
    jdk.tools.jlink.internal.plugins.IncludeLocalesPlugin,
    jdk.tools.jlink.internal.plugins.GenerateJLIClassesPlugin,
    jdk.tools.jlink.internal.plugins.ReleaseInfoPlugin,
    jdk.tools.jlink.internal.plugins.ClassForNamePlugin;
}

Erzeugen eines Laufzeit-Images mit jlink

In diesem Abschnitt zeige ich ein Beispiel für das Erzeugen eines benutzerdefinierten Laufzeit-Images. Unsere Anwendung speichert einen String in einer Datei und dann in einer Datenbank. Wir definieren vier Module und verwenden das ServiceLoader-API, für das wir uns absichtlich entschieden haben, denn es gibt eine wichtige Einschränkung: jlink bietet keine Bindung von Services. Das bedeutet, dass die Module, die durch die Anweisungen uses und provides entdeckt wurden, nicht zum Laufzeit-Image von jlink hinzugefügt werden. jlink fügt in dem Laufzeit-Image nur die Module hinzu, die durch die Anweisungen requires in den Moduldeskriptoren definiert wurden. Natürlich werden auch die transitiven Abhängigkeiten dieser Modulen zum Laufzeit-Image hinzugefügt. Wir definieren schließlich zwei Schnittstellen: DatenbankPersistenzService und DateiPersistenzService im Modul com.javausergroup.service (Listing 2).

// DatenbankPersistenzService.java
package com.javamagazin.service.schnittstellen;
public interface DatenbankPersistenzService {
  void speichernNachrichtImDatenbank(String nachricht);
}

// DateiPersistenzService.java
package com.javamagazin.service.schnittstellen;
public interface DateiPersistenzService {
  void speichernNachrichtImDatei(String nachricht);
}

Diese Schnittstellen enthalten Definitionen von Methoden zum Speichern einer Nachricht in die Datenbank, bzw. zum Speichern einer Nachricht in eine Datei. Der Deskriptor des Moduls com.javamagazin.service ist:

module com.javamagazin.service {
  exports com.javamagazin.service.schnittstellen;
}

Weiter wird ein Teil der Klasse DatenbankPersistenzProvider vom Modul com.javamagazin.datenbankpersistenz angezeigt (Listing 3).

package com.javamagazin.datenbankpersistenz;
import com.javamagazin.service.schnittstellen.*;
import java.sql.*;

public class DatenbankPersistenzProvider implements DatenbankPersistenzService {

  ............................
  public void speichernNachrichtInDatenbank(String nachricht) {
    String insertSql = "INSERT INTO NACHRICHTEN(INHALT) VALUES(" + nachricht + ")";

    try {
      connection = DriverManager.getConnection(JDBC_URL, "root", "passwort");
      Statement statement = connection.createStatement();
      int result = statement.executeUpdate(insertSql);
      if (result > 0) {
        System.out.println("Nachricht wurde erfolgreich gespeichert in der Datenbank");
      } else {
        System.out.println("Nachricht konnte nicht in der Datenbank gespeichert werden");
      }        
  } catch (SQLException sqlException) {
      sqlException.printStackTrace();
    }
  }
}

Diese Klasse ist ein so genannter Serviceprovider, der die Methode speichertNachrichtInDatenbank der Schnittstelle DatenbankPersistenzService implementiert. Die Klasse importiert auch das Paket java.sql. Im Anschluss stellen wir den Deskriptor des Moduls com.javamagazin.datenbankpersistenz vor:

module com.javamagazin.datenbankpersistenz {

  requires com.javamagazin.service;
  requires java.sql;
  provides com.javamagazin.service.schnittstellen.DatenbankPersistenzService with com.javamagazin.datenbankpersistenz.DatenbankPersistenzProvider;
}

Dieses Modul benötigt das Modul com.javamagazin.service, da es seine Schnittstelle verwendet, sowie das Modul java.sql, da es Typen aus diesem Modul verwendet. Die provides-Anweisung bedeutet, dass unser Modul die Implementierung der DatenbankPersistenzService-Schnittstelle mit der DatenbankPersistenzProvider-Klasse bereitstellt. Weiter wird ein Teil der Klasse DateiPersistenzProvider vom Modul com.javamagazin.dateipersistenz dargestellt (Listing 4).

package com.javamagazin.dateipersistenz;
import com.javamagazin.service.schnittstellen.*;
import java.io.*;

public class DateiPersistenzProvider implements DateiPersistenzService {
  .......
    public void speichernNachrichtInDatei(String nachricht) {
      try {
        fileWriter = new FileWriter(FILENAME);
        bufferedWriter = new BufferedWriter(fileWriter);
        bufferedWriter.write(message);
      } 
      catch (IOException e) {
        e.printStackTrace();
      } 
      finally {
        try {
          if (bufferedWriter != null)
              bufferedWriter.close();
          if (fileWriter != null)
              fileWriter.close();
        } catch (IOException ex) {
          ex.printStackTrace();
      }
    }
}

Diese Klasse ist ein Serviceprovider, der die Methode speichernNachrichtInDatei() der Schnittstelle DateiPersistenzService implementiert. Hier der Deskriptor des Moduls com.javamagazin.dateipersistenz:

module com.javamagazin.dateipersistenz {
  requires com.javamagazin.service;
  provides com.javamagazin.service.schnittstellen.DateiPersistenzService with com.javamagazin.dateipersistenz.DateiPersistenzProvider;
}

Dieses Modul benötigt das Modul com.javamagazin.service und erklärt, dass es die Implementierung der DateiPersistenzService-Schnittstelle mit der DateiPersistenzProvider-Klasse bereitstellt. Die Main-Klasse des Moduls com.javamagazin.applikation wird in Listing 5 dargestellt.

package com.javamagazin.applikation;
import com.javamagazin.service.schnittstellen.*;
import java.util.ServiceLoader;

public class Main {
  public static void main(String[] args) {
    DateiPersistenzService dateiPersistenzService = ServiceLoader.load(DateiPersistenzService.class).iterator().next();
    dateiPersistenzService.speichernNachrichtImDatei("Nachricht zum Speichern");

    DatenbankPersistenzService datenbankPersistenzService = ServiceLoader.load(DatenbankPersistenzService.class).iterator().next();
  datenbankPersistenzService.speichernNachrichtImDatenbank("Nachricht zum Speichern ");
  }
}

 

Diese Klasse verwendet das ServiceLoaderAPI, um die Schnittstellen DateiPersistenzService und DatenbankPersistenzService zu laden. Es ruft dann die entsprechenden Methoden auf den Schnittstellen auf. Als Ergebnis wird die Nachricht in der Datenbank bzw. in der Datei gespeichert. Der Deskriptor des Moduls com.javamagazin.applikation ist:

module com.javamagazin.applikation {
  requires com.javamagazin.service;
  uses com.javamagazin.service.schnittstellen.DateiPersistenzService;
  uses com.javamagazin.service.schnittstellen.DatenbankPersistenzService;
}

Wir kompilieren alle Java-Klassen und verwenden dafür die neue Option –module-source-path:

javac -d output --module-source-path src $(find . -name "*.java")

Das output-Verzeichnis enthält alle kompilierten Klassen unserer vier Module. Weiterhin erstellen wir ein maßgeschneidertes Laufzeit-Image mit jlink, indem wir den folgenden Befehl ausführen:

jlink --module-path "output;$JAVA_HOME/jmods" --add-modules com.javamagazin.applikation --output laufZeitImage

Es ist noch wichtig zu erwähnen, dass wir in unserem Beispiel das Windows-Betriebssystem verwenden. Wir geben zwei Verzeichnisse für den Modulpfad an. Das output-Verzeichnis enthält die Klassendateien aller Module. JAVA_HOME ist eine Umgebungsvariable, die auf eine Java-9-Installation verweist. Sie besteht unter anderem aus dem Verzeichnis jmods, das die Module des JDKs enthält. Mit der Option –add-modules können wir den Namen des Root-Moduls angeben. Schließlich gibt die –output-Option das Verzeichnis an, in dem das neue benutzerdefinierte Laufzeit-Image erstellt wird. Das jlink-Werkzeug analysiert zunächst den Deskriptor des Moduls com.javamagazin.applikation und fügt dann rekursiv alle Module hinzu, die das Laufzeit-Image benötigt. Das laufZeitImage-Verzeichnis enthält nun das neu erzeugte Laufzeit-Image. Wir können herausfinden, welche Module unser Laufzeit-Image enthält, indem wir den Befehl java –list-modules innerhalb des laufzeitImage-Verzeichnisses ausführen:

cd laufZeitImage

./bin/java --list-modules

Das Ergebnis ist:

com.javamagazin.applikation

com.javamagazin.service

java.base

 

Unser Laufzeit-Image enthält aktuell nur diese drei Module. Das Modul java.base ist das einzige Plattformmodul, das immer automatisch zum Laufzeit-Image hinzugefügt wird. Weiterhin haben wir zwei Applikationsmodule in unserem Laufzeit-Image: com.javamagazin.applikation, das dem Root-Modul entspricht, das wir mit dem Flag –add-options angegeben haben. Dieses Modul braucht das Modul com.javamagazin.service in seinem Moduldeskriptor. Als Ergebnis haben wir auch dieses Modul als Teil unseres Laufzeit-Images.

Wir merken jedoch, dass die Module com.javamagazin.datenbankpersistenz und com.javamagazin.dateipersistenz nicht in unserem neu erstellten Laufzeit-Image vorhanden sind, obwohl sie zuvor erfolgreich im Verzeichnis output kompiliert wurden. Weiterhin sollte auch das Modul java.sql im Laufzeit-Image vorhanden sein, da es vom Modul com.javamagazin.datenbankpersistenz benötigt wird. Der Grund, warum diese Module nicht im Laufzeit-Image vorhanden sind, liegt daran, dass jlink die Services nicht bindet. jlink identifiziert die notwendigen Module durch die Suche nach den requires-Klauseln innerhalb des Moduldeskriptors. Es sucht nicht nach den uses- und provides-Klauseln. Das ist eine bewusste Beschränkung von jlink. In unserem Modul com.javamagazin.applikation benötigen wir die Module com.javamagazin.datenbankpersistenz und com.javamagazin.dateipersistenz nicht. Als Schlussfolgerung werden sie nicht zum Laufzeit-Image hinzugefügt. Glücklicherweise gibt es eine einfache Lösung, um die benötigten Module in das Laufzeit-Image einzufügen. Man kann sie mit der Option –add-modules spezifizieren:

jlink --module-path "output;$JAVA_HOME/jmods"
  --add-modules com.javamagazin.applikation
  --add-modules com.javamagazin.datenbankpersistenz
  --add-modules com.javamagazin.dateipersistenz
  --output laufZeitImage

Wenn wir z. B. das jmods-Verzeichnis in unserem Modulpfad nicht erwähnen, wird ein Fehler geworfen:

Error: module java.sql not found, required by com.javamagazin.datenbankpersistenz

Dieser Fehler informiert uns darüber, dass das Modul java.sql, das vom Modul com.javamagazin.datenbankpersistenz benötigt wird, nicht auf dem Modulpfad gefunden wurde, da wir seinen Ort, das jmods Verzeichnis, nicht auf den Modulpfad aufgenommen haben. Der Befehl –list-modules liefert folgendes Ergebnis zurück:

./bin/java --list-modules
com.javamagazin.applikation
com.javamagazin.datenbankpersistenz
com.javamagazin.dateipersistenz
com.javamagazin.service
java.base
java.logging
java.sql
java.xml

Das neue Laufzeit-Image enthält nicht nur die beiden zusätzliche Module, die wir mit den Optionen –add-modules eingefügt haben, sondern auch drei neue Module: java.logging, java.sql und java.xml. Der Grund dafür ist, dass das Modul com.javamagazin.datenbankpersistenz das Modul java.sql benötigt, sodass dieses Modul in das Laufzeit-Image eingefügt wird. Aber das Modul java.sql erfordert die Module java.logging und auch java.xml. Daher werden diese beiden Module auch in das Laufzeit-Image eingefügt. Somit werden alle Abhängigkeiten erfolgreich erfüllt. Die Struktur unseres Laufzeit-Images ähnelt dem JRE. Sie hat die folgende Struktur:

-bin (Verzeichnis)
-conf (Verzeichnis)
-include (Verzeichnis)
-legal (Verzeichnis)
-lib (Verzeichnis)
-release (Datei)

Da wir das Betriebssystem Windows eingesetzt haben, ist unser Laufzeit-Image Windows-spezifisch. Als Nächstes prüfen wir die Größe des neu erzeugten Laufzeit-Images:

$ du –hs

48M

Unser gesamtes Laufzeit-Image hat eine Größe von 48 MB, was viel niedriger ist als die Größe eines ganzen JDKs. Wir löschen jetzt unser Laufzeit-Image und generieren ein neues mit der Option für die Komprimierung, um seine Größe zu reduzieren:

jlink --module-path "output;$JAVA_HOME/jmods" 
--add-modules com.javamagazin.applikation
--add-modules com.javamagazin.datenbankpersistenz
--add-modules com.javamagazin.dateipersistenz
--compress=2
--output laufZeitImage

Wir haben Level 2 der Kompression verwendet. Das neue Laufzeit-Image hat nun eine Gesamtgröße von 29 MB. Durch Kompression haben wir es geschafft, die Größe des Laufzeit-Images um mehr als vierzig Prozent zu reduzieren. Es wird empfohlen, Kompression zu verwenden, um kompakte Laufzeit-Images zu erzeugen, insbesondere, wenn wir sie auf kleinen Geräten installieren müssen.

Das Laufzeit-Image ausführen

Man kann das Laufzeit-Image mit dem folgenden Befehl ausführen:

$ ./bin/java –m com.javamagazin.applikation/com.javamagazin.applikation.Main

Wir zeigen auf den bin-Ordner, der sich im Laufzeit-Image befindet, und rufen dann den Java-Launcher mit der Option –m auf. Diese Option enthält den Modulnamen zusammen mit dem Namen der Hauptklasse. Wir haben angegeben, dass wir die Main-Klasse aus dem Modul com.javamagazin.applikation ausführen wollen. Wichtig: Im Laufzeit-Image befindet sich eine ausführbare Datei, die für eine bestimmte Plattform ausgerichtet ist.

Modulare JAR-Dateien als Eingabe für jlink

Zuerst löschen wir unser Laufzeit-Image, weil wir ein neues auf Basis modularer JAR-Dateien und JMOD-Dateien erzeugen möchten. Wir erstellen modulare JAR-Dateien für jedes Modul nach folgendem Muster:

$ jar --create –file output/com.javamagazin.applikation.jar --main-class com.javamagazin.applikation.Main –C output/com.javamagazin.applikation

Weiterhin führen wir genau den gleichen jlink-Befehl aus wie zuvor, um ein maßgeschneidertes Laufzeit-Image zu erstellen. Das neu erzeugte Laufzeit-Image, das aus modularen JAR-Dateien erstellt wurde, ist wie das Laufzeit-Image aufgebaut, das am Anfang dieses Artikels erstellt wurde.

Aufbau des erzeugten Laufzeit-Images

Das Laufzeit-Image, das wir zuvor erzeugt haben, ist quasi eine kleinere Implementierung von Java, die genau für unseren Quellcode generiert wurde, um ihn erfolgreich zum Laufen zu bringen. Es hat nichts anderes außer den minimalen Bibliotheken, die notwendig sind, um laufen zu können. Das Laufzeit-Image enthält ein paar Ordner. Im bin-Verzeichnis gibt es insgesamt drei Java-Launcher. Das Keytool wird für die Verwaltung von Zertifikaten und anderen sicherheitsrelevanten Sachen eingesetzt, im lib-Verzeichnis kann man alle Klassen und Ressourcen finden. Wichtig hier: Es gibt keine rt.jar- oder tools.jar-Datei. Das conf-Verzeichnis enthält die Benutzerkonfiguration. Alle in diesem Verzeichnis enthaltenen Dateien können vom Benutzer ohne Probleme bearbeitet werden. Das legal-Verzeichnis enthält das Copyright von Oracle.

Keine Unterstützung für die Verknüpfung von automatischen Modulen

Projekt Jigsaw bietet keine Unterstützung für die Verknüpfung von automatischen Modulen. Ein Modul, das keine module-info.class-Datei enthält, kann nicht zu einem Laufzeit-Image hinzugefügt werden. Jeder Versuch führt zu einem Fehler. In unserem vorherigen Beispiel laden wir guava.jar im output-Verzeichnis herunter. Anschließend versuchen wir, das Laufzeit-Image zu erzeugen und fügen dort auch Guava ein:

--add-modules guava

Da guava.jar ein einfaches JAR ist und daher kein module-info.class enthält, wird der folgende Fehler geworfen:

Error: module-info.class not found for guava module

Es gibt allerdings einen berechtigten Grund für die Entscheidung von Oracle, automatische Module nicht mithilfe von jlink zu verknüpfen. Wie wir schon wissen, können die automatischen Module auf den Klassenpfad zugreifen. Infolgedessen kann niemand davon ausgehen, dass ein von jlink erstelltes maßgeschneidertes Laufzeit-Image korrekt funktioniert, wenn es auch automatische Module enthält. Da die automatischen Module für die Durchführung der Migration eingesetzt werden, können sie auf den Klassenpfad Verweise auf verschieden Typen haben. Um die automatischen Module in jlink trotzdem verwenden zu können, können wir module-info.class-Dateien zu jedem der vorhandenen JAR-Dateien hinzufügen und dazu das Werkzeug jdeps mit der Option –generate-module-info verwenden.

jlink Plug-ins

jlink enthält ein paar wichtige Plug-ins, die von Entwicklern erweitert werden können. Wir können zum Beispiel unsere eigenen jlink-Plug-ins zur Optimierung der Laufzeit-Images entwickeln. Wir werden uns drei Plug-ins genauer anschauen: Compress, Release Info und Excludes Files. Das Compress-Plug-in hat die Aufgabe, alle Ressourcen im Laufzeit-Image zu komprimieren. Die Syntax des Plug-ins ist einfach: –compress=<Stufe>. Es gibt insgesamt drei Stufen der Komprimierung: 0, 1 und 2. Stufe 1 führt eine ZIP-Komprimierung der Klassen durch. Stufe 2 besteht aus Stufe 0 und Stufe 1. In Tabelle 2 wird ein Vergleich der Größe unseres Laufzeit-Images vorgestellt.

Stufe für die Komprimierung Größe des Laufzeit-Images
Keine Komprimierung 48 MB
Stufe 0 48 MB
Stufe 1 38 MB
Stufe 2 29 MB

Tabelle 2: Vergleich der Größe unseres Laufzeit-Images

Das Release-Info-Plug-in druckt nützliche Informationen zu dem Laufzeit-Image. In diesem Beispiel wollen wir sehen, was die Releasedatei unseres Laufzeit-Images enthält:

runtimeImage>cat release
OS_NAME="Windows"
MODULES="java.base com.javamagazin.service com.javamagazin.applikation java.logging com.javamagazin.dateipersistenz java.xml java.sql com.javamagazin.datenbankpersistenz
OS_VERSION="5.2"
OS_ARCH="amd64"
JAVA_VERSION="9"
JAVA_FULL_VERSION="9-ea"

Wir können neue Eigenschaften in der Releasedatei mit dem folgenden Befehl hinzufügen:

--release-info add:<key>=<value>

Das Exclude-Files Plug-in ermöglicht uns, Dateien aus dem Laufzeit-Image auszuschließen. Im nächsten Beispiel möchten wir alle *.diz-Dateien aus unserem Laufzeit-Image ausschließen. Die *.diz-Dateien sind komprimiert und Informationsdateien fürs Debugging:

--exclude-files *.diz

Fazit

jlink hat als Kommandozeilenprogramm angefangen, wurde nach einer Weile aber schon ein Standard. Das Tool ist insbesondere geeignet, wenn wir beabsichtigen, ein maßgeschneidertes ausführbares Image zu erstellen.

Verwandte Themen:

Geschrieben von
Alexandru Jecan
Alexandru Jecan
Alexandru Jecan ist Diplom-Informatiker und hat mehr als sechs Jahre Erfahrung im IT-Bereich mit Fokus auf Architektur, Softwareentwicklung und Qualitätssicherung von Enterprise-, Web-, Client-Server- und Mobile-Anwendungen.
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.