筆者はアセンブリ言語が好きです。
javaにも中間言語のようなものが無いかと調べてみるとありました。
せっかくなので中身を覗いてみます。
バイトコード生成の手順
javaをコンパイルしてclassファイルを生成します。
その後javapを使うと簡単にバイトコードが見られます。
public class test {
public static void main(String[] args){
int a = 10;
int b = 20;
int c = a + b;
System.out.println(c);
}
}
test@test-ThinkPad-X280:~/test/javatest$ nano test.java
test@test-ThinkPad-X280:~/test/javatest$ javac test.java
test@test-ThinkPad-X280:~/test/javatest$ java test
30
test@test-ThinkPad-X280:~/test/javatest$ javap -v test
Classfile /home/test/test/javatest/test.class
Last modified 2025年8月23日; size 400 bytes
SHA-256 checksum b69fb5d281787a43a8e3cd9bcf0d2e5e96e6deea9ffa7a80086f8e972eb08a71
Compiled from "test.java"
public class test
minor version: 0
major version: 61
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #19 // test
super_class: #2 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream;
#8 = Class #10 // java/lang/System
#9 = NameAndType #11:#12 // out:Ljava/io/PrintStream;
#10 = Utf8 java/lang/System
#11 = Utf8 out
#12 = Utf8 Ljava/io/PrintStream;
#13 = Methodref #14.#15 // java/io/PrintStream.println:(I)V
#14 = Class #16 // java/io/PrintStream
#15 = NameAndType #17:#18 // println:(I)V
#16 = Utf8 java/io/PrintStream
#17 = Utf8 println
#18 = Utf8 (I)V
#19 = Class #20 // test
#20 = Utf8 test
#21 = Utf8 Code
#22 = Utf8 LineNumberTable
#23 = Utf8 main
#24 = Utf8 ([Ljava/lang/String;)V
#25 = Utf8 SourceFile
#26 = Utf8 test.java
{
public test();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=1
0: bipush 10
2: istore_1
3: bipush 20
5: istore_2
6: iload_1
7: iload_2
8: iadd
9: istore_3
10: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
13: iload_3
14: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
17: return
LineNumberTable:
line 7: 0
line 8: 3
line 9: 6
line 10: 10
line 12: 17
}
SourceFile: "test.java"
定数プール
バイトコードから直接参照されている要素
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#7 = Fieldref #8.#9 // java/lang/System.out:Ljava/io/PrintStream;
#13 = Methodref #14.#15 // java/io/PrintStream.println:(I)V
#7を組み立てる
#8 = クラス → クラス名は "java/lang/System"
#9 = 名前+型 → フィールド名 "out", 型 "Ljava/io/PrintStream;"
#8 = Class #10 // java/lang/System
#9 = NameAndType #11:#12 // out:Ljava/io/PrintStream;
#10 = Utf8 java/lang/System
#11 = Utf8 out
#12 = Utf8 Ljava/io/PrintStream;
#13を組み立てる
#14 = クラス → クラス名 "java/io/PrintStream"
#15 = 名前+型 → メソッド名 "println", メソッド記述子 "(I)V"
#14 = Class #16 // java/io/PrintStream
#15 = NameAndType #17:#18 // println:(I)V
#16 = Utf8 java/io/PrintStream
#17 = Utf8 println
#18 = Utf8 (I)V
(I)Vの意味
括弧の中 () = 引数の型
括弧の外(右側) = 戻り値の型
→ 「intを引数に取り、戻り値は voidのメソッド」
バイトコード解釈
コンストラクタ
javaのコードに書いていなくても自動的にコンストラクタが補完される模様。
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
逆コンパイルしてもコンストラクタのjavaコードは出てこなかったが多分こんな感じ。
public test() { super(); }
変数a,bの代入
0: bipush 10 // スタックに 10
2: istore_1 // ローカル変数1 (a) に格納
3: bipush 20 // スタックに 20
5: istore_2 // ローカル変数2 (b) に格納
ここまでで a=10, b=20 が確定。
変数a,bの加算
6: iload_1 // ローカル変数1 をスタックに積む (a)
7: iload_2 // ローカル変数2 をスタックに積む (b)
8: iadd // スタック上の 2 値 (a+b) を加算し積む
9: istore_3 // ローカル変数3 (c) に格納
ここで c = a + b が計算完了。
Sysyem.out.println()の呼び出し
10: getstatic #7 // スタックに System.out (PrintStream型オブジェクト)
13: iload_3 // スタックに c (int)
14: invokevirtual #13 // PrintStream.println(int) 呼び出し
17: return
#7 は「System.out」という static フィールドの参照情報
#13 は「PrintStream.println(int)」というメソッドの参照情報
逆コンパイル
wget https://www.benf.org/other/cfr/cfr-0.152.jar -O cfr.jar
java -jar cfr.jar test.class --outputdir ./src
/*
* Decompiled with CFR 0.152.
*/
public class test {
public static void main(String[] stringArray) {
int n = 10;
int n2 = 20;
int n3 = n + n2;
System.out.println(n3);
}
}