ByteScope

Java class ファイル形式

コンパイル済みの Java バイトコード。JVM が実行します。

MIME タイプ
application/java-vm
拡張子
.class
カテゴリ
実行ファイル・バイトコード

マジックナンバー(ファイル署名)

0x00000000CAFEBABE····'CAFEBABE' magic

Java class ファイル形式について

.class ファイルはコンパイル済みの Java の型が 1 つ(クラス、インターフェース、enum、record のいずれか)JVM 自身のコンテナ形式に収まったもので、その中身のほとんどは 1 つの構造、すなわち定数プールです。クラス名、メソッド名、フィールドのディスクリプタ、文字列リテラル、あらゆるシンボリック参照がここに番号付きのエントリとして入り、ファイルの残りはそれらをテキストではなくインデックスで参照します。class ファイルが JAR の中でよく縮むのも、クラス名を変えるとファイル中のあちこちのバイトが変わるのも、定数プールより後ろのオフセットがすべて可変なのもこのためです。プールの長さは、その型が言及する名前の数だけで決まります。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
04magicCA FE BA BE。Java にまだ名前が無かった頃に James Gosling が選んだもので、間の悪いことに Apple のユニバーサルバイナリと共有しています。ファイル中で値が固定されている唯一のフィールドです。
42minor_versionほぼ常に 0。値 65535 には特別な意味があり、そのクラスがプレビュー機能を使っていて、コンパイルしたのとまったく同じ JDK でしかロードできないことを示します。メジャーバージョンが合っているのにプレビュー機能のクラスが読み込めない、という現象の正体はこれです。
62major_versionclass ファイルのバージョンで、あらゆる UnsupportedClassVersionError の背後にあるフィールドです。Java 5 以降はフィーチャーリリース番号 + 44 で、Java 8 が 52、Java 17 が 61、Java 21 が 65。ランタイムが他の何かを読む前に、自分の上限と比べるのがこの 2 バイトです。
82constant_pool_countプールのエントリ数より 1 大きい値です。プールが 1 始まりで、インデックス 0 は予約されているからです。小さなユーティリティクラスなら数十、大きなフレームワークのクラスは上限の 65535 に近づくこともあり、生成コードでは実際にぶつかることのある制限です。
10variableconstant_poolエントリが隙間なく並び、それぞれ長さを決める 1 バイトのタグで始まります。タグ 1 は 2 バイトの長さ接頭辞を持つ UTF-8 文字列、タグ 7 はクラス参照、タグ 10 はメソッド参照、タグ 8 は文字列。タグ 5 と 6(long と double)はインデックスの枠を 2 つ消費します。この形式の中で、手書きのパーサーを一番多く壊してきた規則です。
variable2access_flagsプールの直後の最初のフィールドです。0x0001 が public、0x0010 が final、0x0200 が interface、0x4000 が enum。そして 0x0020(ACC_SUPER)は、旧来の invokespecial の意味論のために 1997 年以降にコンパイルされたほぼすべてに立っています。素の public クラスなら通常 0x0021 と読めます。
variable2this_classこの型の名前を持つプールエントリのインデックスで、ドットではなくスラッシュを使う内部形式です。JAR の中でのファイルのパスと一致していなければならず、ディスク上で .class ファイルをリネームすると、両方の綴りを並べた NoClassDefFoundError が出るのはそのためです。
variable2super_classスーパークラスのエントリのインデックスで、通常は java/lang/Object。0 になるのは java/lang/Object 自身だけで、これは世界で唯一、親を持たないクラスです。
variable2interfaces_countこの後ろに続く直接のスーパーインターフェースの数で、それぞれ 2 バイトのプールインデックスです。そこからフィールド、メソッド、クラス属性が続き、どれも長さ接頭辞付きなので、プール以降のファイルは順番に読むしかありません。目次にあたるものはありません。

似た形式との見分け方

紛らわしい形式見分け方
Mach-O(ユニバーサル)どちらも CA FE BA BE で始まります。4〜7 バイト目を読んでください。class ファイルは minor_version、major_version の順で、major_version は最低 45(Java 1.1)、今なら 60 台から 70 台です。ユニバーサルバイナリはそこに nfat_arch、つまり小さなアーキテクチャ数が入るので上位半分はゼロ。6〜7 バイト目が 44 より大きい非ゼロなら class ファイルです。
Java JARJAR はオフセット 0 に class ファイルを持ちません。50 4B 03 04 で始まる ZIP で、圧縮された class ファイルを中に抱えています。JAR のバイト列に CA FE BA BE が現れるのは想定どおりで、ただ先頭には無いというだけ。アーカイブの正体を示すのはセントラルディレクトリのエントリ名です。
Android DEXAndroid のツールは .class ファイルをコンパイルしてから捨てます。d8 がそれらをまとめて DEX ファイルにするのです。DEX は 'dex' と改行で始まり、JVM のスタックマシンではなくレジスタベースの命令セットを使います。だから APK には .class のエントリが 1 つも入っておらず、Android 向けの JAR と APK を見分ける一番手早い方法がこれです。
Java source (.java)ビルドディレクトリで混ざるので書いておきます。.java ファイルはプレーンテキストですが、.class ファイルは UTF-8 テキストとして妥当でないバイトで始まります。.class を開いてクラス名やメソッド名を読める形で表示するツールは、ソースではなく定数プールの UTF-8 エントリを読んでいます。

よくある壊れ方

  • ランタイムより新しい major_version。破損ではまったくないのですが、人が眺めているのはたいていこれです。JVM は 6〜7 バイト目を読み、自分の上限より上のバージョンを見つけると、他を一切パースせずに UnsupportedClassVersionError を投げます。release 21 でコンパイルして Java 17 で動かすと、まさにこうなります。
  • 途中切れ。定数プールより後ろはすべて長さ接頭辞付きで順番に並んでいるので、切られた class ファイルはそのとき読んでいた構造の場所で ClassFormatError になり、メッセージが本当の原因を指すことはめったにありません。メッセージを読むより、ファイルが短いことに気づくほうが早いです。
  • 定数プールの中のバイトを編集した。文字列リテラルを別の長さのものに変えて 2 バイトの長さ接頭辞を直さないと、以降のエントリが全部同期を失い、パーサーは名前の途中からタグを読んで「未知の定数タグ」と報告します。class ファイルへのパッチに hex エディタではなくライブラリが要るのはこのためです。
  • テキストモード転送。古い FTP の問題は、レガシーなビルドパイプラインの中で今も class ファイルに届きます。CR LF 変換は最初の出現より後ろを全部ずらし、定数プールが長さ接頭辞付きエントリの連なりであるために、失敗はマジックナンバー不一致のような綺麗な形ではなく、意味不明なタグとして現れます。

この形式の現在地

class ファイルは JVM の実際のインターフェースです。Java も Kotlin も Scala も Groovy も Clojure もこれにコンパイルされ、だからこそ 5 つではなく 1 つの生態系になっています。2005 年にコンパイルされたライブラリが今日のコードとリンクできるのも、この形式が安定しているからです。単体で扱うことは稀で、クラスは JAR や WAR、モジュールイメージ、コンテナのレイヤーの中を移動します。それでも、バイトコードツールも難読化ツールもカバレッジエージェントも、実行時にプロキシを生成するフレームワークも、実際に触っているのはこれです。目立った例外が Android で、ビルド時に class ファイルを取り込み、出荷するのは DEX です。hex エディタを持つ人にとって有用な性質は、class ファイルがかなりの部分そのまま読めるテキストだということ。定数プールはその型が触れるクラス・メソッド・フィールドをすべて綴っているので、逆コンパイルしなくても依存関係が読めます。

Java class ファイルをこのサイトで開く

どれもブラウザ内で動きます。ファイルが端末から出ることはありません。

関連する形式

Java class ファイルと混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
Java JAR50 4B 03 04.jar
Mach-O(ユニバーサル)CA FE BA BE.dylib
Android DEX64 65 78 0A.dex

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

Java class ファイルかもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。