サンプルコードで身につける: ソースが動くまで: javac と .class と JVM
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1最小の Java プログラムを読む
Java のコードは必ずの中に書き、実行は という決まった形のから始まります。実行すると Hello, sum=5 と出力されます。このコースの実測は、この Hello を class にして Hello.java に保存したものを使っています。
2.class の先頭8バイトと、版が合わないとき
同じ Hello.java を 25 と JDK 22 でコンパイルし、先頭を16進で見たものです。どちらも cafe babe で始まり、違うのは版を表す番号だけでした(0x45 は 69、0x42 は 66)。JDK 25 で既定のまま作った を JDK 22 で実行すると読み込みの段階で拒否され、 に 22 を付けて作り直すと番号は 66 になり、JDK 22 でも動きました。
# JDK 25 の javac で作った Hello.class00000000: cafe babe 0000 0045 0032 0a00 0200 0307 .......E.2......# JDK 22 の javac で作った Hello.class00000000: cafe babe 0000 0042 0032 0a00 0200 0307 .......B.2......# JDK 25 で作った Hello.class を JDK 22 の java Hello で実行Error: LinkageError occurred while loading main class Hellojava.lang.UnsupportedClassVersionError: Hello has been compiled by a more recent version of the Java Runtime (class file version 69.0), this version of the Java Runtime only recognizes class file versions up to 66.0
3バイトコードを javap で読む
JDK 付属の で .class の命令列を表示したものです。add は iload_0、iload_1、iadd、ireturn の4命令で、これは CPU ではなく に向けた命令です。main では 2 と 3 を積んでから add を呼んでおり、add(2, 3) は 5 に置き換えられていません。
static int add(int, int);descriptor: (II)Iflags: (0x0008) ACC_STATICCode:stack=2, locals=2, args_size=20: iload_01: iload_12: iadd3: ireturn...public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: (0x0009) ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: iconst_21: iconst_32: invokestatic #7 // Method add:(II)I5: istore_16: getstatic #13 // Field java/lang/System.out:Ljava/io/PrintStream;9: iload_110: invokedynamic #19, 0 // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;15: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/String;)V18: return
4javac が畳むもの、畳まないもの
javac がコンパイル時に計算を済ませたのは定数式だけで、2 + 3 は iconst_5 に、"a" + "b" は ab という1つの文字列になっています。メソッド呼び出しの add(2, 3) と、何もしないループはそのまま残りました。 は受け付けられますが、出力された .class は付けない場合と1バイトも違いませんでした(JDK 25)。
static int viaConstant();Code:0: iconst_51: ireturnstatic int viaCall();Code:0: iconst_21: iconst_32: invokestatic #7 // Method add:(II)I5: ireturnstatic java.lang.String s();Code:0: ldc #13 // String ab2: areturn
public class Fold {
static final int K = 2 + 3; // 定数式
static int add(int a, int b) { return a + b; }
static int viaConstant() { return 2 + 3; } // 定数式: javac が畳む
static int viaCall() { return add(2, 3); } // メソッド呼び出し: 呼び出しのまま残る
static String s() { return "a" + "b"; } // 定数式
static int deadLoop() { int x = 0; for (int i = 0; i < 10; i++) { x += 0; } return x; }
}
5Java・Rust・Node で、実行に何が要るか
同じ Hello を3つの言語で用意し、と実行に必要なものを並べた実測です。JVM の入っていない node:24 のでは Hello は command not found になり、Rust の実行ファイルはコピーするだけで動きました。Rust の大きさは での値で、rustc を直接使うと約 4.57 MB になるなど条件で変わります。
Java Rust Node.jsビルド javac Hello.java cargo build --release なし成果物 Hello.class 919 B hello 469,008 B hello.js 97 B先頭バイト CA FE BA BE 7F 45 4C 46 (ELF) (テキスト)中身 バイトコード aarch64 の機械語 JavaScript のソース実行に要るもの JVM OS と libc node実行に要るものの大きさ(測った範囲が違うので、そのままは比べられない)Java: libjvm.so 27,630,008 B、lib/modules 146,097,979 B(JDK 全体は du -sh で 253M)Node: node の実行ファイル 122,893,672 B