Java JAR ファイル形式
Java Archive。コンパイル済みの .class ファイルとマニフェストを束ねた ZIP。
- MIME タイプ
- application/java-archive
- 拡張子
- .jar
- カテゴリ
- アーカイブ・圧縮
マジックナンバー(ファイル署名)
| 0x00000000 | 504B0304 | PK·· | 'PK' — it is a ZIP |
ZIP 内に 'META-INF/MANIFEST.MF' と 1 つ以上の '.class' エントリがあります。
Java JAR 形式について
JAR は、ZIP の上に取り決めを 1 つ載せたものです。META-INF ディレクトリの MANIFEST.MF がアーカイブを説明します。実行可能 JAR ならメインクラス、クラスパス、モジュール名、そして署名済み JAR なら全エントリのダイジェスト。ZIP としては他に何も変わらないので、どんなアーカイブツールでも JAR を開けますし、この形式が WAR や EAR、Spring Boot の fat JAR、そして歴史的には Android の APK にまで使い回されてきたのもそのためです。落とし穴は、その取り決めにエントリの順序まで含まれていること。ストリーミングで読む実装はマニフェストをファイルの一番前に探しに行き、そこに無ければあっさり諦めます。
ヘッダーの構造(バイト単位)
| オフセット | 長さ | フィールド | 意味 |
|---|---|---|---|
| 0 | 4 | Local file header signature | 50 4B 03 04。JAR はバイト単位で ZIP であり、オフセット 0 に Java の話は出てきません。どのエントリもこのヘッダで始まり、位置が固定されているのは最初のものだけです。 |
| 4 | 2 | Version needed to extract | 無圧縮なら 10、deflate なら 20。jar ツールが書いた JAR の最初のエントリは META-INF/ というディレクトリで、これは無圧縮なので、中身が全部 deflate されているファイルでもこのフィールドはよく 10 と読めます。 |
| 6 | 2 | General purpose bit flag | 0x0800(ビット 11)はエントリ名が UTF-8 であることを示し、JDK の jar ツールはこれを立てます。ビット 3、つまりサイズをデータの後ろへ押しやるストリーミング用のフラグは、その場で JAR を生成するサーバーの出力に現れます。 |
| 8 | 2 | Compression method | 0 が無圧縮、8 が deflate。META-INF/ のようなディレクトリのエントリは長さ 0 で無圧縮格納されるので、JAR の最初のエントリが CRC もサイズ 2 つもゼロなのは、どこも壊れていない正常な姿です。 |
| 10 | 4 | Last modified time and date | MS-DOS のパック形式で、時刻がオフセット 10、日付が 12。分解能 2 秒、タイムゾーンなし、1980 年より前は表現できません。再現可能ビルドはこれを定数に固定するので、やたら多くの JAR が 1980-01-01 や 2010-01-01 生まれを名乗っています。 |
| 14 | 4 | CRC-32 | 非圧縮エントリの CRC-32 で、圧縮後サイズが 18、非圧縮サイズが 22。署名済み JAR では、これらは META-INF の SHA ダイジェストとは独立なので、CRC は通るのに署名検証には失敗するエントリがあり得ます。 |
| 26 | 2 | File name length | オフセット 30 にある名前のバイト長。jar ツールが書いた JAR なら 9(META-INF/ の長さ)で、hex ダンプ上の非公式ながら手早い目印になります。 |
| 30 | variable | First entry name | 実務上、JAR が自分の正体を名乗る場所です。META-INF/ に続いて META-INF/MANIFEST.MF。この順序は飾りではありません。ストリーミングで読む実装は、マニフェストをファイルの先頭部分でしか探さないからです。 |
| -22 | 4 | End of central directory signature | 50 4B 05 06。アーカイブコメントが無ければ末尾からちょうど 22 バイトの位置です。クラスローダーはここからセントラルディレクトリを読みます。60 MB の JAR から 1 クラスだけを、残りを展開せずに読み込めるのはそのおかげです。 |
| -6 | 4 | Offset of the central directory | セントラルディレクトリの開始位置を、ファイル先頭から数えたもの。シェルスクリプトを前置きした自己展開型・実行可能型の JAR が動くのは、読み手がそのずれを許容しているからで、この値自体は生のファイル配置と一致しなくなっています。 |
似た形式との見分け方
| 紛らわしい形式 | 見分け方 |
|---|---|
| ZIP | バイトレベルでは同一です。ZIP を JAR にしているのは、セントラルディレクトリにある META-INF/MANIFEST.MF と、通常は .class エントリの存在です。JAR を .zip にリネームしても何も変わりませんし、ZIP を .jar にリネームすれば、java -jar が「メインマニフェスト属性が無い」と言って拒否するファイルができるだけです。 |
| Android APK | APK は JAR として生まれた経緯があり、今も META-INF を持つので、アーカイブビューアでは似て見えます。分かれ目は中身で、.dex エントリと AndroidManifest.xml があれば APK、.class エントリと MANIFEST.MF だけなら JAR。そしてセントラルディレクトリの前に 'APK Sig Block 42' のマジックナンバーを持つのは APK だけです。 |
| war / ear | 形式は同じで、要求されるレイアウトが違います。WAR はクラスを WEB-INF/classes、ライブラリを WEB-INF/lib に置き、EAR は application.xml と他のアーカイブを収めます。サーブレットコンテナが見るのは拡張子ではなくレイアウトなので、名前を間違えた WAR は ZIP まわりではなく WEB-INF が無いことで失敗します。 |
| Spring Boot jar | 自分のクラスを BOOT-INF/classes に置き、依存を BOOT-INF/lib の中に入れ子の JAR として持つ実行可能 JAR で、MANIFEST.MF の Main-Class に指定されているのはランチャーです。普通の JAR として開けますが、アプリケーションのクラスがクラスローダーの通常の探索先にないので、展開してメインクラスを直接実行しても動きません。 |
よくある壊れ方
- マニフェストが先頭でなくなった。汎用の ZIP ツールで JAR を作り直すと中身のバイトはすべて保たれますが順序は失われ、ストリーミングで読む実装は「マニフェストが無い」と報告します。JDK 自身の API で測ってみたところ、jar ツールが書いた JAR(META-INF/ の次に META-INF/MANIFEST.MF)はマニフェストを返し、class ファイルの後ろにマニフェストを 2 番目・3 番目のエントリとして置いたアーカイブはどちらも null を返しました。java -jar はそれでも動きます。ストリーミングではなくセントラルディレクトリを読むからです。同じファイルが、読み方によって動いたり失敗したりするわけです。
- 末尾の切れ。ローカルヘッダはすべて残っていてオフセット 0 から見れば健全に見えますが、end-of-central-directory レコードが失われており、末尾から読み始めるクラスローダーはエントリを 1 つも列挙できません。復旧は 50 4B 03 04 を前方に探してディレクトリを組み直す作業です。
- 再パッケージ後に署名が壊れた。署名済み JAR は META-INF/MANIFEST.MF に全エントリのダイジェストを持ち、その隣に署名ファイルを置きます。shade したり圧縮し直したりマージしたりするとダイジェストが変わり、JVM は破損ではなくセキュリティ例外としてアーカイブを拒否します。
- 雑なマージで生まれた重複エントリ。依存を 1 つのアーカイブに連結すると、同じパスのエントリが 2 つできることがあります。ZIP はこれを許し、クラスローダーは最初の 1 つを採るので、どのバージョンのクラスが勝つかはセントラルディレクトリ上の順序で決まります。ファイルとしては正当なのに、目に見えない欠陥を抱えている状態です。
この形式の現在地
JAR は JVM の生態系まるごとにとっての配布単位です。Maven Central にあるものはすべて JAR、ビルドツールが解決する依存もすべて JAR、サーバーサイドの Java アプリケーションも、素のライブラリであれコンテナ用の WAR であれ自己完結した Spring Boot のアーカイブであれ、JAR として出荷されます。取って代わったものはありません。モジュールシステムは新しいコンテナではなく JAR の中の module-info.class を足しましたし、native-image や jlink も上流を置き換えるのではなく JAR から実行ファイルを作ります。地盤を失ったのはクライアント側の配信です。アプレットは消え、Java Web Start も一緒に消えたので、今日の JAR はサーバーかビルド時の成果物です。生き続けている強みは ZIP のセントラルディレクトリで、クラスローダーが何千ものクラスの中から 1 つだけを、残りに触れずに取り出せます。
Java JAR ファイルをこのサイトで開く
どれもブラウザ内で動きます。ファイルが端末から出ることはありません。
関連する形式
Java JAR と混同しやすい形式です。先頭バイトを見比べてください。
| 形式 | 先頭バイト | 拡張子 |
|---|---|---|
| ZIP | 50 4B 03 04 | .zip |
| Android APK | 50 4B 03 04 | .apk |
| Java class ファイル | CA FE BA BE | .class |