はじめに
以前自分の blog にて JDK に付属しているツールである javapackager について紹介したことがあります。このツールは主にクライアントサイド Java アプリケーションを配布可能な形態でパッケージングするためのツールです。ネイティブインストーラも生成することができます。
このエントリではネイティブパッケージに含まれるランタイムについて、次のようなことを述べていました。
昔は JDK を丸ごと放り込むという豪快な感じになっていましたが、最近は結構スリムアップしました。JDK9 の Jigsaw が入るともっと効率よくなるでしょう。
そして遂に Java9 がリリースされました。JDK9 の javapackager のマニュアル には次のような記載があります。
For self-contained applications, the Java Packager for JDK 9 packages applications with a JDK 9 runtime image generated by the jlink tool.
確かに jlink と連動してランタイムイメージを作ると記載されています。つまり、module-info.java を作っていれば、必要なモジュールだけを含んだランタイムイメージを作成することになり、アプリケーションの配布サイズが小さくなることが期待されます。というわけで早速 JDK9 の javapackager を試してみることにしました。
パッケージ対象となるアプリケーション
まず、パッケージ対象となるアプリケーションを作ります。とてもシンプルな JavaFX アプリケーションとして作ります。
package aoetk.sample; import javafx.application.Application; import javafx.scene.Scene; import javafx.scene.layout.StackPane; import javafx.scene.text.Text; import javafx.stage.Stage; public class SampleApp extends Application { @Override public void start(Stage primaryStage) throws Exception{ primaryStage.setTitle("Packager Sample"); StackPane stackPane = new StackPane(); stackPane.getChildren().add(new Text("Packager Sample")); primaryStage.setScene(new Scene(stackPane, 300, 275)); primaryStage.show(); } public static void main(String[] args) { launch(args); } }
ウィンドウの真ん中にテキストを表示するだけのとてもシンプルな JavaFX アプリケーションです。これを対象にパッケージングを行ってみます。
JDK8でのパッケージング
まず、JDK8 でコンパイルし、パッケージングしてみます。パスを JDK8 に通します。
>set PATH=C:\Program Files\Java\jdk1.8.0_144\bin;%PATH% >javac -version javac 1.8.0_144 >where javapackager C:\Program Files\Java\jdk1.8.0_144\bin\javapackager.exe
JDK8 の javac でコンパイルし、JAR を作ります。
>javac -d bin src\aoetk\sample\SampleApp.java >javapackager -createjar -nocss2bin -appclass aoetk.sample.SampleApp -srcdir bin -outdir artifact -outfile packager-sample.jar >dir artifact ドライブ C のボリューム ラベルは Windows です ボリューム シリアル番号は ACFE-5623 です C:\Users\aoe\develop\packager-sample\artifact のディレクトリ 2017/10/08 00:32 <DIR> . 2017/10/08 00:32 <DIR> .. 2017/10/08 00:32 1,354 packager-sample.jar 1 個のファイル 1,354 バイト 2 個のディレクトリ 366,187,679,744 バイトの空き領域
この JAR に対し、ネイティブインストールイメージを作ります。今回はインストール後のイメージサイズを知りたいので、 -native
の引数に image
を渡してインストールイメージのみを作ります。
>javapackager -deploy -native image -outdir package -outfile packager-sample -srcdir artifact -srcfiles packager-sample.jar -appclass aoetk.sample.SampleApp -name "jdk8-sample" -title "JDK8Sample" -BappVersion=1.0 -Bwin.menuGroup="JDK8Sample" アプリケーション・バンドルを作成しています: C:\Users\aoe\develop\packager-sample\package内のjdk8-sample "モジュール: [java.rmi, java.sql, javafx.web, jdk.charsets, java.logging, java.xml.crypto, java.xml, jdk.xml.dom, jdk.jfr, java.datatransfer, jdk.packager.services, jdk.httpserver, javafx.base, jdk.net, java.desktop, java.naming, javafx.controls, java.prefs, java.security.sasl, jdk.naming.rmi, jdk.zipfs, java.base, jdk.crypto.ec, jdk.management.agent, java.management, java.sql.rowset, javafx.swing, jdk.jsobject, jdk.sctp, java.smartcardio, jdk.unsupported, jdk.jdwp.agent, jdk.scripting.nashorn, java.instrument, java.security.jgss, jdk.management, java.compiler, javafx.graphics, jdk.security.auth, java.scripting, javafx.fxml, jdk.dynalink, javafx.media, jdk.accessibility, java.management.rmi, jdk.naming.dns, jdk.security.jgss, jdk.localedata]をランタイム・イメージに追加しています。" 警告: Windows Defenderが原因でJavaパッケージャが機能しないことがあります。問題が発生した場合は、リアルタイム・モニタリン グを無効にするか、ディレクトリ"C:\Users\aoe\AppData\Local\Temp\"の除外を追加することにより、問題に対処できます。 結果のアプリケーション・バンドル: C:\Users\aoe\develop\packager-sample\package
これで jdk8-sample
ディレクトリの下にインストールイメージが作られます。これはインストール後にインストールディレクトリに展開される構成そのものです。
ディレクトリサイズは次のように 167MB と、単純なアプリケーションにしては随分大きなサイズになっていることが分かります。
JDK9でのパッケージング
では Java9 で導入されたモジュールシステムを利用してパッケージングしてみることにしましょう。環境を JDK9 に変更します。
>set PATH=C:\Program Files\Java\jdk-9\bin;%PATH% >javac -version javac 9 >where javapackager C:\Program Files\Java\jdk-9\bin\javapackager.exe
モジュールの設定を行います。まずはこのアプリケーションがどのモジュールを利用しているかを調べてみましょう。
>jdeps -s artifact\packager-sample.jar packager-sample.jar -> java.base packager-sample.jar -> javafx.base packager-sample.jar -> javafx.graphics
java.base
モジュールの他に javafx.base
モジュール、 javafx.graphics
モジュールに依存していることが分かります。 javafx.graphics
モジュールは javafx.base
モジュールに依存しているので module-info.java
は次のように javafx.graphics
モジュールへの依存を記載すれば OK です。
module aoetk.sample.packager { requires javafx.graphics; exports aoetk.sample; }
これを JDK9 のコンパイラを使ってコンパイルし、JAR を作ります。
>javac -d bin src\module-info.java src\aoetk\sample\SampleApp.java >javapackager -createjar -nocss2bin -appclass aoetk.sample.SampleApp -srcdir bin -outdir artifact -outfile packager-sample.jar >dir artifact ドライブ C のボリューム ラベルは Windows です ボリューム シリアル番号は ACFE-5623 です C:\Users\aoe\develop\packager-sample\artifact のディレクトリ 2017/10/08 00:52 <DIR> . 2017/10/08 00:52 <DIR> .. 2017/10/08 00:52 1,642 packager-sample.jar 1 個のファイル 1,642 バイト 2 個のディレクトリ 366,004,936,704 バイトの空き領域
これを同じように javapackager を用いてインストールイメージを作るのですが、モジュールを使ったアプリケーションをパッケージングする場合は指定する引数が異なります。ですが、現在の javapackager のマニュアル はこの変更に追いついていません...。執筆時点で javapackager のコマンドラインオプションについて正確な記述があったのは JEP 275: Modular Java Application Packaging のみでした。
具体的には -srcdir
、 -srcfiles
、 -appclass
の指定が無くなり、モジュールを使った Java アプリケーションを java
コマンドで実行するときと同じようにモジュールパス (対象モジュールの JAR が置かれているディレクトリ) を -p
(もしくは --module-path
) で、実行クラスを -m
(もしくは --module
) でモジュール名とクラス名を組み合わせて指定します。
>javapackager -deploy -native image -outdir package -outfile packager-sample -p artifact -m aoetk.sample.packager/aoetk.sample.SampleApp -name "jdk9-sample" -title "JDK9Sample" -BappVersion=1.0 -Bwin.menuGroup="JDK9Sample" アプリケーション・バンドルを作成しています: C:\Users\aoe\develop\packager-sample\package内のjdk9-sample モジュールaoetk.sample.packagerは存在しません。 "モジュール: [aoetk.sample.packager]をランタイム・イメージに追加しています。" モジュールaoetk.sample.packagerは存在しません。 警告: Windows Defenderが原因でJavaパッケージャが機能しないことがあります。問題が発生した場合は、リアルタイム・モニタリン グを無効にするか、ディレクトリ"C:\Users\aoe\AppData\Local\Temp\"の除外を追加することにより、問題に対処できます。 結果のアプリケーション・バンドル: C:\Users\aoe\develop\packager-sample\package
これで jdk9-sample
ディレクトリの下にインストールイメージが作られます。
ディレクトリサイズを見ると 84.9MB と JDK8 の場合に比べて半分程度になっていることが分かります。確かに Jigsaw の効果が出ていますね!
インストールイメージの中身について
なお、インストールイメージの中身を調べてみると、こちらも興味深いものがありました。 アプリケーションや JRE の JAR が見当たらない のです! runtime
ディレクトリの下を覗いてみると、何やら modules
というそれっぽいファイルがあります。
このファイル、JAR か JMOD ファイルかと思いきや、特に ZIP 圧縮されていません。バイナリエディタで覗いてみると時々 CAFEBABE が登場しており、単にクラスファイルを 1 つのファイルにまとめたもののように見受けられます。色々調べてみましたが、このファイルが何であるかの解説を見つけられませんでした。誰か知っている人いますか?
(2017/10/10) 追記
id:MATSUZAKI 様よりコメントで情報を頂きました。jimage 形式のファイルであるとのことです。jimage については Java Magazine の Vol.25 に説明がありました。
jimage形式は、モジュール化されたランタイムに必要なクラスやリソースを管理するコンテナの形式です。 jimageファイルは、従来のようなzipベースの圧縮ではなく、クラスやリソースを高速に検索できるようにインデックスが付けられています。jimageのコンテンツ領域には、そのイメージのすべてのクラスとリソースが含まれており、位置情報にひも付けて管理されています。
JDK には jimage というコマンドがあったので (なお、現時点での JDK9 のドキュメントにはこのコマンドについての説明は見当たらず...) 、このコマンドで中身を閲覧してみました。
>jimage list modules jimage: modules Module: aoetk.sample.packager META-INF/MANIFEST.MF aoetk/sample/SampleApp.class module-info.class Module: java.base META-INF/services/java.nio.file.spi.FileSystemProvider com/sun/crypto/provider/AESCipher$AES128_CBC_NoPadding.class com/sun/crypto/provider/AESCipher$AES128_CFB_NoPadding.class com/sun/crypto/provider/AESCipher$AES128_ECB_NoPadding.class ...
確かにクラスファイルやリソースファイルが含められていますね。
まとめ
ということで、Project Jigsaw の恩恵で、Java SE 9 からはアプリケーションのインストールイメージをより絞って配布が可能であることが分かりました。Oracle も Oracle Java SEサポート・ロードマップ において、Java アプリケーションの配布は (予め配布先に Java をインストールさせるのではなく) JRE も一緒にバンドルした自己完結型パッケージングでの配布を推奨しています。jlink と javapackager を最大限に活用してきましょう。