Upgrade to Pro — share decks privately, control downloads, hide ads and more …

セキュリティ・キャンプ 2026 ミニ(愛知開催)講義スライド

セキュリティ・キャンプ 2026 ミニ(愛知開催)講義スライド

Java 仮想マシン(JVM)のバイト・コードを実際に手書きし,良き JVM になるための講義です。
講義資料から大部分を抜粋して掲載します。

一部若干のアニメーションを使用した箇所が破損していますが,ご了承ください。
(Java の講義をするなどのために,必要でしたらご連絡ください)

Avatar for Daisuke Yamazaki

Daisuke Yamazaki

September 12, 2026

More Decks by Daisuke Yamazaki

Other Decks in Education

Transcript

  1. 00 HELLO 自己紹介 山﨑 大介 筑波大学 情報学群 情報科学類 𝕏️ @yamad_javad

    【略歴】 ・2024年 情報処理安全確保支援士 登録 ・2025年 ネットワーク・スペシャリスト試験 合格 ・ 同 セキュリティ・キャンプ 全国大会 参加 ・ 同 OpenJDK Author(JDK) ・2026年 筑波大学 情報学群 情報科学類 入学 ・ 同 OpenJDK Author(Valhalla) 第 028545 号 【趣味・特技】 小学生の頃に Java に魅了 気づけは OpenJDK に貢献 Sun Microsystems に魅了され Java や Sun のグッズを集める Java Ring に出会い解析中に 探偵!ナイト・スクープに。 無事復活に成功し,Java ヲタクに
  2. 00 REVIEW 復習をしよう Java のプログラムは Java 仮想マシンが実行する! class Main {

    void main() { IO.println("Hello!"); } } CA FE BA BE 00 00 00 46 00 0A 0A 00 02 00 03 07 … クラス(メタ情報やバイト・コードなど) が所定の形式で入っている。
  3. 01 JVM//BASICS Java 仮想マシンとは Java Virtual Machine • Java のプログラムを実行する仮想的なコンピュータ

    • クラス・ファイルを読み込む • 中に記録されたバイト・コードを命令として実行する • Java Virtual Machine Specification で決まっている ↑機能や命令が定義されている • 誰でもインターネットで閲覧できる セキュリティ・キャンプ 2025 に持参したもの
  4. 01 JVM//BASICS Java 仮想マシンとは Write Once, Debug Everywhere • 例えば

    C のコード。 • Windows では windows.h を,UNIX では unistd.h を include しないといけない • Sleep(Windows) と sleep(UNIX) がある • Sleep はミリ秒で指定 • sleep は秒で指定 • 本当に OS ごとにこれをやるの..? 環境差異を吸収したい! #include <stdio.h> #ifdef _WIN32 #include <windows.h> #else #include <unistd.h> #endif int main(void) { #ifdef _WIN32 printf("Windows¥n"); Sleep(1000); // ミリ秒で指定 #else printf("Unix-like OS¥n"); sleep(1); // 秒で指定 #endif return 0; }
  5. 01 JVM//BASICS Java 仮想マシンとは Write Once, Run Anywhere Intel x86

    マシン x86 用 JVM Main.java class Main { void main() { int i = 0; i += 2; } } Windows Main.class javac class Main { main()V { iconst_0 istore_1 iload_1 iconst_2 iadd istore_1 } } 【バイト・コード】 JVM 専用の命令たち java arm マシン arm 用 JVM Linux SPARC マシン sparc 用 JVM SunOS
  6. 01 JVM//BASICS Java 仮想マシンとは Write Once, Run Anywhere Intel x86

    マシン x86 用 JVM Windows Main.class 共通のバイナリを… 異なる CPU / 異なる OS で実行できる! class Main { main()V { iconst_0 istore_1 iload_1 iconst_2 iadd istore_1 } } java arm マシン arm 用 JVM SPARC マシン sparc 用 JVM JVM 専用の命令たち Linux SunOS
  7. 01 JVM//BASICS Java 仮想マシンとは いろいろな言語が JVM で動く class Main {

    void main() { int i = 0; i += 2; } } Main.kt class Main { fun main() { var i = 0 i += 2 } } Main.clj JVM class file / bytecode Main.java javac class Main { main()V { iconst_0 istore_1 iload_1 iconst_2 iadd istore_1 kotlinc } } JVM Clojure scalac (ns main (:gen-class)) (defn -main [& args] (let [i 0 i (+ i 2)])) Main.scala object Main { def main(): Unit = { var i = 0 i += 2 } }
  8. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう 3 * 2 + 4

    = ? ←問題 • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 記憶領域はこれだけ! • スタックに対する次の操作だけができる PUSH 1 PUSH 2 PUSH 3 PUSH 4 • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける 3 掛けた値を PUSH する 2 一番上から2つ POP して値を足す 1 • ADD: 4 足した値を PUSH する ↑スタック
  9. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう 3 * 2 + 4

    = ? ←問題 • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 記憶領域はこれだけ! • スタックに対する次の操作だけができる • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける 掛けた値を PUSH する • ADD: 一番上から2つ POP して値を足す PUSH 1 PUSH 2 PUSH 3 PUSH 4 POP POP POP POP 4 3 2 1 足した値を PUSH する ↑スタック
  10. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう 3 * 2 + 4

    = ? ←問題 • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 記憶領域はこれだけ! • スタックに対する次の操作だけができる • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける • ADD: PUSH 3 PUSH 2 掛けた値を PUSH する 2 一番上から2つ POP して値を足す 3 足した値を PUSH する ↑スタック
  11. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 3

    * 2 + 4 = ? ←問題 6+4=? 記憶領域はこれだけ! • スタックに対する次の操作だけができる • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける • ADD: PUSH 3 PUSH 2 MUL 掛けた値を PUSH する 2 一番上から2つ POP して値を足す 3 6 足した値を PUSH する ↑スタック
  12. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 3

    * 2 + 4 = ? ←問題 6+4=? 記憶領域はこれだけ! • スタックに対する次の操作だけができる • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける • ADD: PUSH 3 PUSH 2 MUL PUSH 4 掛けた値を PUSH する 4 一番上から2つ POP して値を足す 6 足した値を PUSH する ↑スタック
  13. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 3

    * 2 + 4 = ? ←問題 6+4=? 記憶領域はこれだけ! • スタックに対する次の操作だけができる • POP: 一番上の値を取り出す • PUSH: 一番上に値を追加する • MUL: 一番上から2つ POP して値をかける • ADD: PUSH 3 PUSH 2 MUL PUSH 4 ADD 掛けた値を PUSH する 4 一番上から2つ POP して値を足す 10 6 足した値を PUSH する ↑スタック
  14. 02 JVM//BYTECODE Java 仮想マシンのアーキテクチャ Java 仮想マシン • スタック・マシン • 値をスタックに積んで計算を進める

    • 命令は… 「スタックから値を取り出し,結果を積む」 x86 (例) • Java バイト・コードを箱庭の中で逐次実行 • x86 の機械語を CPU が直接実行 【例】 初期 ① ② • レジスタ・マシン • 値をレジスタに保持して計算 • 命令は… 「レジスタの値を読んで,レジスタに格納」 ③ ① iconst_1 ② iconst_2 【例】 ① mov eax, 1 ② add eax, 2 2 ③ iadd 1 1 わかりにくい! EAX 1→3 EBX … ECX … ︙ 3 1 + 2 を計算 EIP …
  15. 02 JVM//BYTECODE Java バイト・コード入門 コンピュータになってみよう • あなたはコンピュータ(スタック・マシン) • 1つのスタックが与えられている 3

    * 2 + 4 = ? ←問題 6+4=? 記憶領域はこれだけ! • スタックに対する次の操作だけができる • pop 一番上の値を取り出す • iconst_N 一番上に整数値を追加する • imul 一番上から2つ POP して値をかける 掛けた値を PUSH する • iadd 一番上から2つ POP して値を足す 足した値を PUSH する iconst_3 iconst_2 imul iconst_4 iadd 10 21 ↑スタック
  16. 02 JVM//BYTECODE Java バイト・コード入門 Java の命令 • JVM の命令はオペコード(opcode)とオペランド(operand)からできている。 •

    オペコード: • オペランド: 命令の種類を示す。1 バイト(0x00~0xFF)。 (現在は 202 個を使用中) 命令に与える追加情報; 引数。 有無や長さは,命令ごとに異なる。 • メソッドのあらゆる処理は,202 の命令の組み合わせでできている! 10 64 57 bipush 100 84 pop 01 02 iinc 1 2
  17. 02 JVM//BYTECODE JVM で使える記憶領域 オペランド・スタック ローカル変数 • 前章で扱ったスタックのこと • 計算結果を保存しておく

    • 計算させたい値や,計算結果を積み上げる • 配列のような見た目 • 好きなところにアクセスできる (オペランド・スタックは必ず上から) • 計算途中の値を一時的に保存しておく • 各命令は,オペランド・スタックの上から 値を取り出し,演算結果をまた積む • 名前ではなくインデックスでアクセスする 必ず上から! 2 idiv 10 5 1234 1234 0 1 5 3 2 int a = 5; int b = 3; iconst_5 istore_0 3 iconst_3 istore_1 4 ⋯ ⋯
  18. 02 JVM//BYTECODE JVM で使える記憶領域 フレーム • メソッドの作業用の領域 • ローカル変数とオペランド・スタックを持つ •

    メソッド実行ごとに1つ作られる。 • 他のメソッドのフレームにはアクセスできない =他のメソッドのローカル変数は触れない hoge() メソッドの実行には⋯ static void hoge() { iconst_5 istore_0 iconst_3 iconst_1 ︙ → バイト・コードは現在のフレームを対象に実行される hoge() 用フレーム 1 3 0 5 1 2 3 4 ⋯ ⋯
  19. 02 JVM//BYTECODE 参考:メソッド実行の仕組み JVM スタック • フレームを積み上げておくスタック • 各スレッドごとに1つある •

    一番上のフレームの命令だけが実行される static void main() { foo(); /* … */ return; } static void foo() { /* … */ bar(); } static void bar() { /* … */ return; } foo() 用のフレーム main() 用のフレーム
  20. 02 JVM//BYTECODE 参考:メソッド実行の仕組み JVM スタック • フレームを積み上げておくスタック • 各スレッドごとに1つある •

    一番上のフレームの命令だけが実行される static void main() { foo(); /* … */ return; } static void foo() { /* … */ bar(); } static void bar() { /* … */ return; } 呼び出されるとフレームが積まれる! フレームを積みすぎると StackOverflowError が発生! bar() 用のフレーム foo() 用のフレーム main() 用のフレーム
  21. 02 JVM//BYTECODE 参考:メソッド実行の仕組み JVM スタック • フレームを積み上げておくスタック • 各スレッドごとに1つある •

    一番上のフレームの命令だけが実行される static void main() { foo(); /* … */ return; } static void foo() { /* … */ bar(); } static void bar() { /* … */ return; } 終了すると取り除かれる bar() 用のフレーム foo() 用のフレーム main() 用のフレーム
  22. 02 JVM//BYTECODE ここまでのまとめ JVM スタック これ以上積めないと StackOverflowError! • JVM はスタック・マシンである。

    • 命令はオペコードとオペランドからなる。 • 計算はオペランド・スタック,保存はローカル変数で • これらはフレームの中にある。 bar() 用のフレーム 1 0 5 3 1 ⋯ ⋯ 呼び出し push 終了 pop • メソッド呼び出しでフレームが積まれる。 foo() • 一番上のフレームだけが実行対象になる main() 用のフレーム 用のフレーム
  23. 03 DECOMPILE//JAVA 観察してみよう とりあえずコンパイルして実行 • Calc.java の中身を見てみると⋯ • 引数 a

    と b を取る • a と b の和を計算する • 和を2倍して返す $ javac Calc.java $ java Calc public class Calc { public static int calc(int a, int b) { return (a + b) * 2; } static void main() { IO.println(calc(0, 1)); IO.println(calc(1, 2)); IO.println(calc(3, 4)); IO.println(calc(4, 5)); } }
  24. 03 DECOMPILE//JAVA 観察してみよう 解析してみよう • Calc.class の中身を見てみよう • JDKの javap

    コマンドを使います public class Calc { public static int calc(int a, int b) { return (a + b) * 2; } $ javap -v Calc static void main() { IO.println(calc(0, 1)); IO.println(calc(1, 2)); IO.println(calc(3, 4)); IO.println(calc(4, 5)); } }
  25. 03 DECOMPILE//JAVA 観察してみよう javap: クラス宣言 • クラスのメタ・データが書いてある。 • バージョンや修飾子,親クラスの名前など •

    親クラスの名前は Constant Pool エントリへの参照 ⋯ public class Calc minor version: 0 major version: 70 flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #8 // Calc super_class: #2 // java/lang/Object interfaces: 0, fields: 0, methods: 3, ⋯ ⋯
  26. 03 DECOMPILE//JAVA 観察してみよう javap: Constant Pool(CP) • クラスで使用する文字列やメソッド参照,クラス参照 などの実体が入っている •

    命令のオペランドとして,CP エントリへの参照が 与えられることも多々ある ⋯ 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 = Methodref #8.#9 // Calc.calc:(II)I #8 = Class #10 // Calc #9 = NameAndType #11:#12 // calc:(II)I ⋯
  27. 03 DECOMPILE//JAVA 観察してみよう javap: メソッド宣言 • メソッドのメタ・データと命令列など ⋯ public static

    int calc(int, int); descriptor: (II)I flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=2 0: iload_0 1: iload_1 2: iadd 3: iconst_2 4: imul 5: ireturn LineNumberTable: line 3: 0 ⋯
  28. 03 DECOMPILE//JAVA 観察してみよう javap: Constant Pool(CP) との関わり • 命令のオペランドとして,CP エントリへの参照が

    与えられることも多々ある • "Hello, World!" の文字列は CP 13番エントリに(厳密には 14 番の UTF8エントリに)格納されている ldc #13 で,CP から文字列をスタックへ積む ⋯ Constant pool: #7 = Fieldref #13 = String #15 = Methodref #8.#9 #14 #16.#17 // java/lang/System.out:Ljava/io/PrintStream; // Hello, World! // java/io/PrintStream.println:(Ljava/lang/String;)V ... public static void main(java.lang.String[]); Code: 0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #13 // String Hello, World! 5: invokevirtual #15 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return ⋯
  29. 04 TRACE//JAVA 命令列を追う JVM になろう! 必要なもの・・・ • 鉛筆&消しゴム • 命令列が書いてあるプリント

    • Jaspera https://jal.yamad.jp/jaspera/ 進めかた・・・ • ドキュメントを参照しながら,命令列を追おう! 分からないことがあれば,LLM や人間に相談ください • プリントに書いてある指示に従ってください
  30. 04 TRACE//JAVA 命令列を追う Jaspera • バイト・コードの多機能 Web エディタ • 命令のドキュメントが使用例とともに。

    • 知らない命令があったら調べてみよう! • Jaspera にアクセス → 右タブ「命令辞書」をクリック → 検索ボックスで命令を検索 → ドキュメントを読んでみよう https://jal.yamad.jp/jaspera/
  31. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] → top [1] [ [ [ [ [ 戻り値:
  32. 04 TRACE//JAVA JVM あれこれ 引数の受け渡し • メソッドの引数はローカル変数として明け渡される。 → 同じ命令で引数とローカル変数を読める! インスタンス・メソッドの呼び出し

    static メソッドの呼び出し static void myMethod(int, float); void myMethod(int, float); myMethod(123, 1.23f); this.myMethod(123, 1.23f); 0 1 123 1.23 int float 2 3 ⋯ 0 1 2 ⋯ this 123 1.23 ref int float 3 ⋯ ⋯
  33. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [ [ [ [ [ 戻り値:
  34. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [ [ [ [ [ 戻り値:
  35. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令「Tload_X」や「Tload」 参考: 命令「Tstore」や「Tstore」 • ローカル変数配列の指定番地の値をスタックに積む

    • スタックから1つ取り出し,ローカル変数へ格納 • T は型の名前(e.g. i は int; 整数) • T は型の名前 • X は指定する番地 • X は指定する番地 0 1 1 2.0 … … 0 1 1 2.0 … … istore_0 iload_0 0 1 1 2.0 … … 5 1 0 5 1 2.0 … … 5
  36. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [ [ [ [ [ 戻り値:
  37. 04 TRACE//JAVA 余談: JVM あれこれ JVM で扱える型 int 系命令あれこれ •

    多くの命令には,扱える型の名前が接頭辞に。 • 以下の型は,原則 int として扱われる: byte, short, char, boolean • Java の型が基本的に揃っている(例外あり!) • 四則演算などの演算は,int 系の命令を使う 型 接頭辞 参照型 a aload, areturn, … byte b baload, bastore char c caload, castore double d dreturn, dadd, dcmpl, … float f freturn, fconst_0, fneg, … int i iload, iinc, if_icmpge, … long l lload, lneg, lcmp, ladd, … short s saload, sastore 使用例 • これらの比較命令も int 系の命令を使う boolean b = true; char c = 'a'; iconst_1 istore_0 bipush 97 istore_1
  38. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [ [ [ [ 戻り値:
  39. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 [ [ [ 戻り値:
  40. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 3 5 [8 [ [ 戻り値:
  41. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 [8 [8 [ 戻り値:
  42. 04 TRACE//JAVA JVM あれこれ 定数を積む命令 型 範囲 iconst_* -1~5 bipush

    sipush 使用例 iconst_m1, iconst_5 -128~127 bipush 94 -32768 ~32767 sipush -32768 ldc 全般 ldc 1200, ldc "abc" aconst_null null aconst_null
  43. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 [8 [8 2 [ 戻り値:
  44. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 [8 [8 2 [16 8 2 戻り値:
  45. 04 TRACE//JAVA 命令列を追う 例 プログラム: Example.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(3, 5) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラン ド・スタックの状態,およびメソッドの戻り値を示せ。 オペランド・スタック ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 iadd 3 iconst_2 4 imul 5 ireturn [0] [1] 3 5 → top [3 [3 5 [8 [8 2 [16 戻り値: 16
  46. 04 TRACE//JAVA 命令列を追う JVM になろう! 必要なもの・・・ • 鉛筆&消しゴム • 命令列が書いてあるプリント

    • Jaspera https://jal.yamad.jp/jaspera/ 進めかた・・・ • ドキュメントを参照しながら,命令列を追おう! 分からないことがあれば,LLM や人間に相談ください • プリントに書いてある指示に従ってください
  47. 04 TRACE//JAVA 命令列を追う ヒント① プログラム: Calc.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(21, 3) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラ ンド・スタックの状態,およびメソッドの戻り値を示せ。 ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 isub 3 bipush 6 5 idiv 6 ireturn [0] [1] 21 3 オペランド・スタック → top [21 [21 3 [ [ [ 戻り値:
  48. 04 TRACE//JAVA 命令列を追う ヒント② プログラム: Calc.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(21, 3) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラ ンド・スタックの状態,およびメソッドの戻り値を示せ。 ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 isub 3 bipush 6 5 idiv 6 ireturn [0] [1] 21 3 オペランド・スタック → top [21 [21 3 [ [ [ 戻り値:
  49. 04 TRACE//JAVA 命令列を追う 答え プログラム: Calc.class 26/09/12 static int calc(int,

    int) メソッドのバイト・コードを以下に示す。 calc(21, 3) と呼び出されるとき,実行される各命令を実行した直後のローカル変数の値とオペラ ンド・スタックの状態,およびメソッドの戻り値を示せ。 ローカル変数 バイト・コード 0 iload_0 1 iload_1 2 isub 3 bipush 6 5 idiv 6 ireturn [0] [1] 21 3 オペランド・スタック → top [21 [21 3 [18 [18 6 [3 戻り値: 3
  50. 04 TRACE//JAVA 命令列を追う ヒント① プログラム: Judge.class 26/09/12 static int compute(int)

    メソッドのバイト・コードは以下。compute(256) と呼ばれる オペランド・スタック ローカル変数 バイト・コード → top [0] 256 0 iload_0 1 ifge NonNegative 4 iload_0 5 ineg 6 ireturn 256 NonNegative: 7 iload_0 8 ireturn 戻り値:
  51. 04 TRACE//JAVA 命令列を追う ヒント① プログラム: Judge.class 26/09/12 static int compute(int)

    メソッドのバイト・コードは以下。compute(256) と呼ばれる オペランド・スタック ローカル変数 バイト・コード → top [0] 256 0 iload_0 1 ifge NonNegative 4 iload_0 5 ineg 6 ireturn 256 NonNegative: 7 iload_0 8 ireturn 戻り値:
  52. 04 TRACE//JAVA 命令列を追う 答え プログラム: Judge.class 26/09/12 static int compute(int)

    メソッドのバイト・コードは以下。compute(256) と呼ばれる オペランド・スタック ローカル変数 バイト・コード → top [0] 256 0 iload_0 1 ifge NonNegative 4 iload_0 5 ineg 6 ireturn 256 NonNegative: 7 iload_0 8 ireturn 戻り値:
  53. 04 TRACE//JAVA 命令列を追う 答え プログラム: Judge.class 26/09/12 static int compute(int)

    メソッドのバイト・コードは以下。compute(256) と呼ばれる オペランド・スタック ローカル変数 バイト・コード → top [0] 256 0 iload_0 1 ifge NonNegative 4 iload_0 5 ineg 6 ireturn 256 NonNegative: 7 iload_0 8 ireturn 256 戻り値: 256
  54. 04 TRACE//JAVA 命令列を追う 答え プログラム: Judge.class 26/09/12 static int compute(int)

    メソッドのバイト・コードは以下。compute(-127) と呼ばれる オペランド・スタック ローカル変数 バイト・コード → top [0] -127 -127 0 iload_0 1 ifge NonNegative 4 iload_0 -127 5 ineg 127 6 ireturn 戻り値: 127 NonNegative: 7 iload_0 8 ireturn 戻り値:
  55. 04 TRACE//JAVA 余談: JVM あれこれ 型ごとの命令が完全に揃っていないのはなぜ? (sadd や bload があっても良さそうなのに!)

    • JVM の opcode は1バイト(最大で 256個) → 使い回して節約し,256個を最大限有効活用 • よく使うものは命令化してしまおう → 「-1~5 を積む」「0~3番地のローカル変数を読む」は頻出 → iconst_m1 や iload_0(iload 0 ではない)を作る →オペランドのフェッチ/デコードが不要。CPU クロックの節約 • 命令ダイエット iload 3 iload_3 0x15 0x1d (1byte) 0x03 (2bytes) ↑ JVMS, Java SE 26 Edition, 2026. より引用
  56. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令: 「new」 • ヒープ領域にオブジェクトを新しく作り,参照をスタックに積む •

    作られたオブジェクトは初期化しないと使えない • コンストラクタはまだ呼ばれない new java/lang/Integer ①ヒープ領域に確保 ヒープ領域 ②参照をスタックへ フレーム Integer(未初期化) value = 0 参照
  57. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令: 「invokespecial」 • オブジェクトを初期化する命令 •

    参照とコンストラクタの引数を受け取り, 呼び出す iconst_3 invokespecial Integer→<init>(I)V ②コンストラクタを呼び出し ヒープ領域 ①参照を受け取る フレーム Integer value = 3 参照
  58. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令: 「invokeX」 • 呼び出すメソッドの 種類によって使う命令が違う!

    呼び出し対象 • メソッドを呼び出す命令たち static メソッド invokestatic インスタンス メソッド invokevirtual コンストラクタ invokespecial インタフェース invokeinterface 動的生成されたもの e.g. ラムダ式など invokedynamic
  59. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令: 「invokestatic」 • static メソッドを呼び出す命令

    • 引数すべてをスタックから受け取り実行。戻り値があればスタックに1つ積む • メソッドの呼び出し中は,後続の処理に進まない (復習: フレームと Javaスタック)
  60. 04 TRACE//JAVA 参考: JVM あれこれ メソッド・ディスクリプタ チート・シート 記号 • ()V

    → 引数・戻り値なし • (Ljava/lang/String;)I → String を受け取って int へ • ([IDZ)[[Ljava/lang/String; → int 配列,double, boolean を String の2次元配列へ 型 I int C char S short B byte J long F float D double Z boolean V void L…; 参照型 [… 配列
  61. 04 TRACE//JAVA 参考: JVM あれこれ 新しい命令: 「invokevirtual」 • インスタンス・メソッドを呼び出す命令 •

    基本的には invokestatic と一緒 • ただし,引数の前に「インスタンスへの参照」を受け取る
  62. 04 TRACE//JAVA 命令列を追う 発展的な問い: dup 5 参照を消費して Invoke オブジェクトを初期化 #Invoke

    #Invoke #Invoke #Invoke #Invoke 参照を複製 初期化したオブジェクトを ローカル変数に格納
  63. 05 WRITE//JAVA バイト・コードを手書きする Jaspera • Jaspera でバイト・コードを書いてみましょう。 Jaspera — Through

    bytecode, ad astra • 配布した zip ファイルを適当なフォルダに展開してください。 • Jaspera にアクセスし,「ファイル」→「フォルダーを開く」から開きましょう。
  64. 05 WRITE//JAVA バイト・コードを手書きする Jaspera • ドキュメントを読みながら,課題のプログラムを書いてみよう • 課題は6つ用意しています。 うち3つ以上をやってください。 •

    分からなかったら,人間や AI に聞いてみてください。 AI に答えを聞いてもよいですが,自分でよく考えてみたほうがタノシイですよ。 • Tasks1~Tasks6 はそれぞれ課題1~6に対応。 テスト・コードが入っているので,適宜実行しながら書いてみましょう。