© 2022 NTT DATA Corporation
バイトコードって言葉をよく目にするけど一体何なんだろう?
JJUG CCC 2022 Spring
NTTデータ 阪田 浩一
2022年6月19日
• 想定視聴者: Javaプログラムを書いて実行したことがあればどなたもOK!
• セッション録画公開: 有
• スライド公開: 有 - SlideShare(https://www.slideshare.net/nttdata-tech)を予定
© 2022 NTT DATA Corporation 2
自己紹介
• Javaチャンピオン
• OpenJDK Author
• 通算パッチ 10数個
• 株式会社NTTデータ 所属
• JVMがとにかく好き
Koichi Sakata
阪田 浩一
jyukutyo
© 2022 NTT DATA Corporation 3
このセッションで話すこと
 話すこと
• プログラムを実行するまでの過程
• Javaバイトコードの概要
• Javaバイトコードを確認する方法
• 実は身近なバイトコード操作
© 2022 NTT DATA Corporation 4
このセッションで話すこと 話さないこと
 話すこと
• プログラムを実行するまでの過程
• Javaバイトコードの概要
• Javaバイトコードを確認する方法
• 実は身近なバイトコード操作
 話さないこと
• バイトコード操作の具体的なコードおよび詳細
• クラスファイル自体の内容
• その他上級者が求める範囲の内容
© 2022 NTT DATA Corporation 5
このセッションのゴール
 参加された方が
Javaプログラムを実行する仕組みの一部として
バイトコードの役割や意味を他者に説明できるようになること
 注意事項
• Javaコードはほぼ登場しません
• 説明のため厳密には正確でない表現をすることがあります
© 2022 NTT DATA Corporation 6
プログラムを書いてから
実行するまで
© 2022 NTT DATA Corporation 7
Javaプログラムを書いてから実行するまで
Javaプログラムを書き ファイルに保存する
© 2022 NTT DATA Corporation 8
Javaプログラムを書いてから実行するまで
Javaプログラムを書き ファイルに保存する
コンパイルし クラスファイルを生成する
クラスを指定して実行する
注: Java 11から単一ファイルのとき`java HelloWorld.java`で実行できますが 内部的な処理は上と同一です
プログラムを実行するまでに
Javaではなぜこの手順が必要なの?
© 2022 NTT DATA Corporation 9
ハードウェアでの命令実行
• 機械語(マシン語)しか理解しない
もうJavaじゃなくて機械語でプログラムを書けば
いいんじゃないの?
© 2022 NTT DATA Corporation 10
ハードウェアでの命令実行
• 機械語(マシン語)しか理解しない
• 機械語は0と1からなり 読み書きが困難である
• 機 械 語 は CPU ア ー キ テ ク チ ャ や 使 用 す る OS に
よって異なる
それは辛い…
じゃあJavaはコンパイルすると
機械語を生成するの?
© 2022 NTT DATA Corporation 11
それはC言語
どうしてJavaはこのやり方にしなかったの?
Cプログラムを書き ファイルに保存する
コンパイルし[1] 機械語を含んだ実行可能ファイルを生成する
ファイルを指定して実行する
[1]: プリプロセッサやリンカもコンパイラと一緒に動作しています
© 2022 NTT DATA Corporation 12
コンパイルで機械語を生成することの短所
 コンパイルをした環境(OS CPU)でのみ動作する[1]
• 実行可能ファイルを他の環境に配置しても実行できないということ
- 再コンパイルを必要とする
[1]: 正確には指定することで異なる環境向けにコンパイルできますが、今度はその環境でしか動作しません
© 2022 NTT DATA Corporation 13
コンパイルで機械語を生成することの短所
 コンパイルをした環境(OS CPU)でのみ動作する[1]
• 実行可能ファイルを他の環境に配置しても実行できないということ
- 再コンパイルを必要とする
[1]: 正確には指定することで異なる環境向けにコンパイルできますが、今度はその環境でしか動作しません
開発はmacOS 本番環境はLinux
みたいなことがやりづらそう…
そもそもコンパイルなんてなしにして
プログラム実行時にソースコードから機械語に変えて
実行しちゃえばいいんじゃない?
© 2022 NTT DATA Corporation 14
インタープリタでの実行
def hello(name = "World")
message = "Hello, " + name + ".¥n"
puts message
end
hello() 1
2
3
© 2022 NTT DATA Corporation 15
インタープリタでの実行
def hello(name = "World")
message = "Hello, " + name + ".¥n"
puts message
end
hello() 1
2
3
• プ ロ グ ラ ム 実 行 時 に ソ ー ス コ ー ド [1] を 1 文 ず つ
機械語に変換し実行する(逐次解釈)
• そのためこの手法を取るとコンパイルが不要となる
• 実行時に解釈する処理は重く 動作が遅くなる
インタ
プリタ
実行
[1]: 厳密にはインタプリタの解釈の対象はソースコードに限定されるものではありません(後述)
注: RubyにはJITコンパイルでの実行もあります
© 2022 NTT DATA Corporation 16
2つのやり方とJava
コンパイルして機械語を生成するやり方は
異なる環境で実行できない短所…
インタープリタで逐次解釈するやり方は
動作が遅くなる短所…
© 2022 NTT DATA Corporation 17
2つのやり方とJava
コンパイルして機械語を生成するやり方は
異なる環境で実行できない短所…
インタープリタで逐次解釈するやり方は
動作が遅くなる短所…
それにJavaのこと何も話していないよ!
Javaは2つの方式を合わせ持っている
バイトコードは2つを合わせる手段である
© 2022 NTT DATA Corporation 18
Javaコードと機械語
Javaコード
• 機 械 語 に 変 え る の は
時間がかかる
• テ キ ス ト フ ァ イ ル な の で
どんなOSでも扱える
© 2022 NTT DATA Corporation 19
Javaコードと機械語
Javaコード
• 機 械 語 に 変 え る の は
時間がかかる
• テ キ ス ト フ ァ イ ル な の で
どんなOSでも扱える
機械語
• そ の ま ま ハ ー ド ウ ェ ア で
実行できるため 速い
• 特 定 の 環 境 で の み
動作する
© 2022 NTT DATA Corporation 20
Javaコードと機械語
Javaコード
• 機 械 語 に 変 え る の は
時間がかかる
• テ キ ス ト フ ァ イ ル な の で
どんなOSでも扱える
機械語
• そ の ま ま ハ ー ド ウ ェ ア で
実行できるため 速い
• 特 定 の 環 境 で の み
動作する
ソースコードに比べて
機械語に変換しやすい
ものにすれば
動作もある程度速く
どんな環境でも利用できる
2つの中間に位置する
そんなコードを作ろう!
© 2022 NTT DATA Corporation 21
Javaバイトコード
 コンパイル時に生成する
• javacなどでのコンパイルのこと
© 2022 NTT DATA Corporation 22
Javaバイトコード
 コンパイル時に生成する
• javacなどでのコンパイルのこと
 JVM独自の表現なのでどんな環境でも利用できる
• その代わりバイトコードの実行にはJVMが必要となる
- JVMが多数のプラットフォームをサポートするため大きな問題ではない
© 2022 NTT DATA Corporation 23
Javaプログラムを実行するということ
Javaコード Javaバイトコード
CAFE
BABE
0000
javacなどでの
コンパイル
© 2022 NTT DATA Corporation 24
Javaプログラムを実行するということ
Javaコード 機械語
Javaバイトコード
CAFE
BABE
0000
javacなどでの
コンパイル
JVMの
インタプリタ
注: JVMにはJITコンパイルでの実行もあります
© 2022 NTT DATA Corporation 25
Javaプログラムを実行するということ
Javaコード 機械語
Javaバイトコード
CAFE
BABE
0000
javacなどでの
コンパイル
JVMの
インタプリタ
バイトコードはJavaコードよりは機械語に変換しやすいから
インタプリタでの実行も向上する!
バイトコードは環境に依存しなから
JVMがあればどこでも動く!
注: JVMにはJITコンパイルでの実行もあります
© 2022 NTT DATA Corporation 26
JVMとJavaバイトコード
© 2022 NTT DATA Corporation 27
JVMでの演算
 JVMは「スタック」上で演算をする
• ハードウェア上ではCPUとメモリを使って処理をしているが
計算モデルとしてそう設計されている
- スタックマシン
© 2022 NTT DATA Corporation 28
JVMでの演算
 JVMは「スタック」上で演算をする
• ハードウェア上ではCPUとメモリを使って処理をしているが
計算モデルとしてそう設計されている
- スタックマシン
• 例: 1 + 2の演算イメージ
- 1、2という数値を 順にスタックに置き
+という演算を スタックから2個の数値を取って 計算し結果をスタックに置く
© 2022 NTT DATA Corporation 29
JVMでの演算
 JVMは「スタック」上で演算をする
• もちろんハードウェア上ではCPUとメモリを使って処理をしているが
計算モデルとしてそう設計されている(スタックマシン)
• 例: 1 + 2の演算イメージ
- 最初スタックは空である
- 1という数値を スタックに置く
1
© 2022 NTT DATA Corporation 30
JVMでの演算
 JVMは「スタック」上で演算をする
• もちろんハードウェア上ではCPUとメモリを使って処理をしているが
計算モデルとしてそう設計されている(スタックマシン)
• 例: 1 + 2の演算イメージ
- 1、2という数値を 順にスタックに置く
+という演算は スタックから2個の数値を取り 結果をまたスタックに置く
1
1
2
3
2
1
+
- int add(int a, int b)を使う呼び出し側も同じイメージでよい
* 呼び出されたaddメソッド内の処理は別途あるけれども(詳細は後述)
© 2022 NTT DATA Corporation 31
JVMとJavaバイトコード
 単純に言えばJavaバイトコードはJavaプログラムの内容を
スタックでの処理として表現する
© 2022 NTT DATA Corporation 32
JVMとJavaバイトコード
 単純に言えばJavaバイトコードはJavaプログラムの内容を
スタックでの処理として表現する
• スタックに何かを置いたり(プッシュ) 一番上から取ったり(ポップ)
インスタンスを生成したり メソッドを呼び出したり など
 その表現のために使用するのがバイトコードの命令セット
© 2022 NTT DATA Corporation 33
Javaバイトコードの命令セット
 200種類以上ある
• Java仮想マシン(JVM)仕様に定義されている
- https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5
© 2022 NTT DATA Corporation 34
Javaバイトコードの命令セット
 200種類以上ある
• Java仮想マシン(JVM)仕様に定義されている
- https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5
• 命令コード(オペコード)が1バイト長
- 1バイト = 28 = 256種類までしか定義できないことになる
- Javaのリリース以来 追加した命令コードは1つだけ
* Project Valhallaで2つ追加する見込み
© 2022 NTT DATA Corporation 35
Javaバイトコードの命令セット
 200種類以上ある
• Java仮想マシン(JVM)仕様に定義されている
- https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5
• 命令コード(オペコード)が1バイト長
- 1バイト = 28 = 256種類までしか定義できないことになる
- Javaのリリース以来 追加した命令コードは1つだけ
* Project Valhallaで2つ追加する見込み
• 暗記する必要はとくにない
- 型違い(プリミティブ型各種類と参照型で同じ内容)の命令コードも多い
- スタックでの利用をイメージすると理解しやすい
© 2022 NTT DATA Corporation 36
Javaバイトコードの命令コードの読み方
 接頭辞の例
i... int[1]
l... long
f... float
d... double
[1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有)
© 2022 NTT DATA Corporation 37
Javaバイトコードの命令セットの読み方
 接頭辞の例
i... int[1]
l... long
f... float
d... double
 命令内容の例
const 定数をプッシュ
add,sub,mul,div
2つポップしてそれらで
四則演算し結果をプッシュ
[1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有)
接頭辞にある型と命令を組み合わせで
数が多くなっているのか
命令の例:
 iconst_1: スタックに定数1をプッシュ
 idiv:スタックにあるint値を2つポップして除算
結果をスタックにプッシュ
 fmul: スタックにあるfloat値を2つポップして乗算
結果をスタックにプッシュ
© 2022 NTT DATA Corporation 38
Javaバイトコードの命令セットの読み方
 接頭辞の例
i... int[1]
l... long
f... float
d... double
 命令内容の例
const 定数をプッシュ
add,sub,mul,div
2つポップしてそれらで
四則演算し結果をプッシュ
[1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有)
1 + 2は
1. iconst_1
2. iconst_2
3. iadd
となる
3
2
1
+
1
2
命令コードからスタックを操作する絵を描いてみると
わかりやすいなあ
© 2022 NTT DATA Corporation 39
Javaバイトコードの命令セットの読み方
 接頭辞の例
i... int[1]
l... long
f... float
d... double
a... 参照型
*a... 1文字目の型の配列 ia...だとint配列
 命令内容の例
const 定数をプッシュ
load ローカル変数をプッシュ
store ローカル変数に保存
return 戻り値として返す
2 プリミティブ型変換 (例: i2d)
add,sub,mul,div 四則演算
neg 符号反転
[1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有)
注: 各命令でスタックからのポップやスタックへのプッシュがあります
注: 接頭辞と命令内容のすべての組み合わせが
命令に存在するわけではありません
© 2022 NTT DATA Corporation 40
メソッド処理のバイトコード表現を考えてみよう
int add(int a, int b) {
return a + b;
}
引数aとbはどうなるの?
スタックにプッシュしたい
 録画再生の方は停止して30秒間考えてみましょう
© 2022 NTT DATA Corporation 41
メソッド処理のバイトコード表現を考えてみよう
int add(int a, int b) {
return a + b;
}
a
b
iload_1はローカル変数の1番目を
スタックにプッシュするのか
iconst_1の_1とは違うんだ
1. メソッド開始時スタックは空
2. iload_1
• 引数はローカル変数に保存されている
- インスタンスメソッドではローカル変数の
0番にthis 1番目以降に引数の値が入っている
3. iload_2
- 引数の2つ目である2番目をプッシュ
a
© 2022 NTT DATA Corporation 42
メソッド処理のバイトコード表現を考えてみよう
int add(int a, int b) {
return a + b;
}
a+b
b
a +
a
b
1. メソッド開始時スタックは空
2. iload_1
• 引数はローカル変数に保存されている
- インスタンスメソッドではローカル変数の
0番にthis 1番目以降に引数の値
3. iload_2
4. iadd
5. ireturn
a
メソッドの
呼び出し元へ
© 2022 NTT DATA Corporation 43
組み合わせでない命令の例
 メソッド呼び出し
invokestatic staticメソッド呼出
invokevirtual インスタンスメソッド
invokespecial コンストラクタ、private、
スーパークラスメソッド
invokeinterface インタフェースメソッド
invokedynamic 動的に算出した
コールサイトの呼出[1]
[1]: 複雑な仕組みのためこのセッション内に理解する必要はありません
invoke...はメソッド呼び出しと
捉えておけばいいか
このinvokedynamicこそが
後から追加された唯一のバイトコード
© 2022 NTT DATA Corporation 44
組み合わせでない命令の例
 メソッド呼び出し
invokestatic staticメソッド呼出
invokevirtual インスタンスメソッド
 他
new,newarray インスタンス生成
pop,pop2 スタックからポップ
dup... 複製をプッシュ
putfield,putstatic フィールドに値をセット
ldc... Constant Poolの値をプッシュ
monitorenter,monitorexit ロックの確保、解放
注:すべての命令は
記載していません
invokespecial コンストラクタ、private、
スーパークラスメソッド
invokeinterface インタフェースメソッド
invokedynamic 動的に算出した
コールサイトの呼出[1]
[1]: 複雑な仕組みのためこのセッション内に理解する必要はありません
© 2022 NTT DATA Corporation 45
メソッド呼び出しのバイトコード表現を考えてみよう
 録画再生の方は停止して30秒間考えてみましょう
class Sample {
int add(int a, int b) {
return a + b;
}
}
// 以下はメインメソッドの中とする
Sample s = new Sample();
s.add(1, 2);
引数aとbはどうなるの?
スタックにプッシュしたい
© 2022 NTT DATA Corporation 46
メソッド呼び出しのバイトコード表現を考えてみよう
class Sample {
int add(int a, int b) {
return a + b;
}
}
// 以下はメインメソッドの中とする
Sample s = new Sample();
int a = s.add(1, 2);
#数字 はConstant Pool(定数プール)
を番号で参照している
クラス名 メソッド名なども
CPに保持している
1. new #7
2. dup
3. invokespecial #9
4. astore_1
5. aload_1
6. iconst_1
7. iconst_2
8. invokevirtual #10
9. istore_2
10. return
なぜdupして
複製するの?
© 2022 NTT DATA Corporation 47
インスタンス生成とコンストラクタ呼び出し
 JVMではこの2つは別の処理
Sample s = new Sample();
1. new #7
2. dup
3. invokespecial #9
4. astore_1
Sam
Sam
Sam
newでSampleインスタンスへの
参照を積む
© 2022 NTT DATA Corporation 48
インスタンス生成とコンストラクタ呼び出し
 JVMではこの2つは別の処理
Sample s = new Sample();
1. new #7
2. dup
3. invokespecial #9
4. astore_1
Sam
Sam
Sam
Sam Sam
invokespecialで
コンストラクタ
呼び出し
Sam
ローカル変数1に
保存
 コンストラクタ実行 ローカル変数への保存
それぞれで参照を使用するため 複製して2つ用意した
• 何かをするためにはスタックから取り出さなければならない
newでSampleインスタンスへの
参照を積む
© 2022 NTT DATA Corporation 49
Javaバイトコードを
実際に見てみよう
© 2022 NTT DATA Corporation 50
Javaバイトコードとクラスファイル
 バイトコードはクラスファイルに含まれる
• クラスファイルの中を見てみよう!
© 2022 NTT DATA Corporation 51
クラスファイルの中 - add()を呼び出すクラス
げ、げぇー
iconstとかiaddとかない
どういうこと??
© 2022 NTT DATA Corporation 52
Javaバイトコードとクラスファイル
 バイトコードはクラスファイルに含まれる
 クラスファイルはバイナリファイル
• テキストファイルではない
© 2022 NTT DATA Corporation 53
Javaバイトコードとクラスファイル
 バイトコードはクラスファイルに含まれる
 クラスファイルはバイナリファイル
• テキストファイルではない
• 各バイトコード命令に対応する数字がファイルに含まれる
iconst_1 4 (0x4)
iconst_2 5 (0x5)
iadd 96 (0x60)
今まで出てきたのは
バイトコードのニーモニックということか…
じゃあクラスファイルを逆アセンブルすればいい?
© 2022 NTT DATA Corporation 54
javapコマンド
 クラスファイルを逆アセンブルするツール
• https://docs.oracle.com/en/java/javase/17/docs/specs/man/javap.html
• JDKに含まれる
- JAVA_HOMEにパスを通していればすぐに使える
© 2022 NTT DATA Corporation 55
javapコマンド
 クラスファイルを逆アセンブルするツール
• https://docs.oracle.com/en/java/javase/17/docs/specs/man/javap.html
• JDKに含まれる
- JAVA_HOMEにパスを通していればすぐに使える
• -c: コードを逆アセンブル または -v: 詳細に出力 を使う
• 詳細は--helpで確認できる
© 2022 NTT DATA Corporation 56
javapコマンドでの逆アセンブル
© 2022 NTT DATA Corporation 57
Constant Poolも見れる
 javap -v の出力に含まれる
© 2022 NTT DATA Corporation 58
<init>は
コンストラクタのことを
指しているんだ
© 2022 NTT DATA Corporation 59
バイトコードの楽しみ方
© 2022 NTT DATA Corporation 60
バイトコードの楽しみ方
1. 読む!
2. 書き換える!
© 2022 NTT DATA Corporation 61
バイトコードの楽しみ方
1. 読む!
2. 書き換える!
© 2022 NTT DATA Corporation 62
バイトコードを読む
 Javaの新バージョンリリースで新機能が出た
• 新機能を使うコードを書いてバイトコードを見る
© 2022 NTT DATA Corporation 63
バイトコードを読む
 Javaの新バージョンリリースで新機能が出た
• 新機能を使うコードを書いてバイトコードを見る
 新バージョンで同じ処理でも
バイトコード表現に変更があることも
© 2022 NTT DATA Corporation 64
バイトコードを読む
 Javaの新バージョンリリースで新機能が出た
• 新機能を使うコードを書いてバイトコードを見る
 新バージョンで同じ処理でも
バイトコード表現に変更があることも
- 例: ネストしたクラスを作り インナークラスから
アウタークラスのprivateメソッドを呼び出すコード
* Java 11で変わった(Nestmates/ネストメイト)
© 2022 NTT DATA Corporation 65
Nestmates
public class Outer {
private void m_outerpriv() {
System.out.println("called m_outerpriv");
}
class Inner {
public void test() {
new Outer().m_outerpriv();
}
}
public static void main(String[] args) {
new Outer().new Inner().test();
}
}
© 2022 NTT DATA Corporation 66
Java 8でコンパイルした結果をjavapで見る
public class Outer {
private void m_outerpriv() {
System.out.println("called m_outerpriv");
}
class Inner {
public void test() {
new Outer().m_outerpriv();
}
}
public static void main(String[] args) {
new Outer().new Inner().test();
}
}
© 2022 NTT DATA Corporation 67
© 2022 NTT DATA Corporation 68
Java 8でコンパイルした結果をjavapで見る
 Outerにaccess$000(Outer)
というstaticメソッドが!?
 InnerはOuterインスタンスを生成し
access$000を呼び出している
public class Outer {
private void m_outerpriv() {
System.out.println("called m_outerpriv");
}
class Inner {
public void test() {
new Outer().m_outerpriv();
}
}
public static void main(String[] args) {
new Outer().new Inner().test();
}
}
© 2022 NTT DATA Corporation 69
Java 8でコンパイルした結果をjavapで見る
 Outerにaccess$000(Outer)
というstaticメソッドが!?
 InnerはOuterインスタンスを
生成し access$000を
呼び出している
 なぜこんなことを?
• フィールドまたはメソッドRにアクセス可能
なクラスまたはインタフェースDとは、以下
のことが真となる場合のみに限る。
• Rがprivateでその宣言がDにある。
- インナークラスにこのprivateメソッドは
宣言されていないため直接呼び出せない
- JVM仕様 5.5.4
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-
5.html#jvms-5.4.4
コンパイラが勝手に生成する
メソッドを合成メソッドという
public class Outer {
private void m_outerpriv() {
System.out.println("called m_outerpriv");
}
class Inner {
public void test() {
new Outer().m_outerpriv();
}
}
public static void main(String[] args) {
new Outer().new Inner().test();
}
}
© 2022 NTT DATA Corporation 70
Java 11以降でコンパイルした結果をjavapで見る
public class Outer {
private void m_outerpriv() {
System.out.println("called m_outerpriv");
}
class Inner {
public void test() {
new Outer().m_outerpriv();
}
}
public static void main(String[] args) {
new Outer().new Inner().test();
}
}
© 2022 NTT DATA Corporation 71
Java 11以降でコンパイルした結果をjavapで見る
 何が変わった?
• Java 11でクラスファイルに新しい属性が追加された
- ネスト関係をこの属性で把握できる
$ javap -v Outer
...
NestMembers:
Outer$Inner
$ javap -v Outer¥$Inner
...
NestHost: class Outer
© 2022 NTT DATA Corporation 72
Java 11以降でコンパイルした結果をjavapで見る
 何が変わった?
• Java 11でクラスファイルに新しい属性が追加された
- ネスト関係をこの属性で把握できる
$ javap -v Outer
...
NestMembers:
Outer$Inner
$ javap -v Outer¥$Inner
...
NestHost: class Outer
• JVM仕様5.4.4も変更されている
- R is private and is declared by a class or interface C
that belongs to the same nest as D, according to the
nestmate test below.
* https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-5.html#jvms-
5.4.4
• この変更によりprivateメソッドが
直接呼び出せるようになった
私のブログにも書いています https://www.sakatakoichi.com/entry/java11nestmates
© 2022 NTT DATA Corporation 73
バイトコードの楽しみ方
1. 読む!
2. 書き換える!
ぎょ、ぎょえー
これを手で書き換えるの?
© 2022 NTT DATA Corporation 74
バイトコード操作ライブラリ
 ライブラリを使いJavaコードでバイトコード命令を書き換える
• ASM https://asm.ow2.io/
• CGLIB https://github.com/cglib/cglib
• Javassist https://www.javassist.org/
• Byte Buddy https://bytebuddy.net/#/
ASMはAPIの抽象度が低い
Byte Buddyは抽象度が
高い
© 2022 NTT DATA Corporation 75
バイトコード操作ライブラリ
 ライブラリを使いJavaコードでバイトコード命令を書き換える
• ASM https://asm.ow2.io/
• CGLIB https://github.com/cglib/cglib
• Javassist https://www.javassist.org/
• Byte Buddy https://bytebuddy.net/#/
 操作手順イメージ
1. クラスファイルを読み込む
2. Javaコードでバイトコードを書き換える(処理の追加 変更 削除)
3. 書き換えた内容を適用する
- コンパイル時やビルド時はクラスファイル出力 実行時はクラスロード
ASMはAPIの抽象度が低い
Byte Buddyは抽象度が
高いイメージ
© 2022 NTT DATA Corporation 76
ByteBuddyでの操作サンプルコード
ByteBuddy byteBuddy = new ByteBuddy();
byteBuddy
.redefine(TypePool.Default.of(classLoader).describe("a.b.Hello").resolve(),
ClassFileLocator.ForClassLoader.of(classLoader))
.method(ElementMatchers.named("toString"))
.intercept(FixedValue.value("transformed"))
.make()
.load(classLoader, ClassReloadingStrategy.of(inst));
// inst = java.lang.instrument.Instrumentationオブジェクト
a.b.HelloクラスのtoString()メソッドを
"transformed"と返すように書き換えたって感じに読める
最後はJDKのInstrument APIを使って
書き換えたクラスをロードさせて使われるようにした
© 2022 NTT DATA Corporation 77
ソースコードを直接変更すればいいのでは…?
 そう! でも変更したくない/できないケースもある
• フレームワークやライブラリが アプリケーションで作成したクラスに
対して拡張を施したいとき
- 例: Spring Hibernate
• モックオブジェクトを作るとき
- 例: jMockit Mockito
* テストコード実行時にメソッドの内容をモック用に書き換えてしまうイメージ[1]
普段使っているフレームワークやライブラリでも
バイトコードを操作しているんだ
Bytecode Enhancement とも言う
[1]: 新たにサブクラスを作るなどいろいろなやり方があります
© 2022 NTT DATA Corporation 78
バイトコードを知って何の意味があるの?
 フレームワークが使うだけなら
私たちがバイトコードを知る必要はとくにないのでは…
• 明日からすぐ業務に役立つ内容ではありません
• ただし技術の習得には2つの側面があります
- 車輪の両輪であり 2つをバランス良く習得することが大切です
使
い
方
を
学
ぶ
仕
組
み
を
学
ぶ
• 使用する技術がどのように実現
されているのかを学ぶこと
• 実行の仕組み 設計概念
内部的表現
• 目 的 と す る ア プ リ ケ ー シ ョ ン を
作成するためにそれをどのように
使えばよいかを学ぶこと
• 言 語 構 文 FW の 設 定
ライブラリの使い方
© 2022 NTT DATA Corporation
本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です。

バイトコードって言葉をよく目にするけど一体何なんだろう?(JJUG CCC 2022 Spring 発表資料)

  • 1.
    © 2022 NTTDATA Corporation バイトコードって言葉をよく目にするけど一体何なんだろう? JJUG CCC 2022 Spring NTTデータ 阪田 浩一 2022年6月19日 • 想定視聴者: Javaプログラムを書いて実行したことがあればどなたもOK! • セッション録画公開: 有 • スライド公開: 有 - SlideShare(https://www.slideshare.net/nttdata-tech)を予定
  • 2.
    © 2022 NTTDATA Corporation 2 自己紹介 • Javaチャンピオン • OpenJDK Author • 通算パッチ 10数個 • 株式会社NTTデータ 所属 • JVMがとにかく好き Koichi Sakata 阪田 浩一 jyukutyo
  • 3.
    © 2022 NTTDATA Corporation 3 このセッションで話すこと  話すこと • プログラムを実行するまでの過程 • Javaバイトコードの概要 • Javaバイトコードを確認する方法 • 実は身近なバイトコード操作
  • 4.
    © 2022 NTTDATA Corporation 4 このセッションで話すこと 話さないこと  話すこと • プログラムを実行するまでの過程 • Javaバイトコードの概要 • Javaバイトコードを確認する方法 • 実は身近なバイトコード操作  話さないこと • バイトコード操作の具体的なコードおよび詳細 • クラスファイル自体の内容 • その他上級者が求める範囲の内容
  • 5.
    © 2022 NTTDATA Corporation 5 このセッションのゴール  参加された方が Javaプログラムを実行する仕組みの一部として バイトコードの役割や意味を他者に説明できるようになること  注意事項 • Javaコードはほぼ登場しません • 説明のため厳密には正確でない表現をすることがあります
  • 6.
    © 2022 NTTDATA Corporation 6 プログラムを書いてから 実行するまで
  • 7.
    © 2022 NTTDATA Corporation 7 Javaプログラムを書いてから実行するまで Javaプログラムを書き ファイルに保存する
  • 8.
    © 2022 NTTDATA Corporation 8 Javaプログラムを書いてから実行するまで Javaプログラムを書き ファイルに保存する コンパイルし クラスファイルを生成する クラスを指定して実行する 注: Java 11から単一ファイルのとき`java HelloWorld.java`で実行できますが 内部的な処理は上と同一です プログラムを実行するまでに Javaではなぜこの手順が必要なの?
  • 9.
    © 2022 NTTDATA Corporation 9 ハードウェアでの命令実行 • 機械語(マシン語)しか理解しない もうJavaじゃなくて機械語でプログラムを書けば いいんじゃないの?
  • 10.
    © 2022 NTTDATA Corporation 10 ハードウェアでの命令実行 • 機械語(マシン語)しか理解しない • 機械語は0と1からなり 読み書きが困難である • 機 械 語 は CPU ア ー キ テ ク チ ャ や 使 用 す る OS に よって異なる それは辛い… じゃあJavaはコンパイルすると 機械語を生成するの?
  • 11.
    © 2022 NTTDATA Corporation 11 それはC言語 どうしてJavaはこのやり方にしなかったの? Cプログラムを書き ファイルに保存する コンパイルし[1] 機械語を含んだ実行可能ファイルを生成する ファイルを指定して実行する [1]: プリプロセッサやリンカもコンパイラと一緒に動作しています
  • 12.
    © 2022 NTTDATA Corporation 12 コンパイルで機械語を生成することの短所  コンパイルをした環境(OS CPU)でのみ動作する[1] • 実行可能ファイルを他の環境に配置しても実行できないということ - 再コンパイルを必要とする [1]: 正確には指定することで異なる環境向けにコンパイルできますが、今度はその環境でしか動作しません
  • 13.
    © 2022 NTTDATA Corporation 13 コンパイルで機械語を生成することの短所  コンパイルをした環境(OS CPU)でのみ動作する[1] • 実行可能ファイルを他の環境に配置しても実行できないということ - 再コンパイルを必要とする [1]: 正確には指定することで異なる環境向けにコンパイルできますが、今度はその環境でしか動作しません 開発はmacOS 本番環境はLinux みたいなことがやりづらそう… そもそもコンパイルなんてなしにして プログラム実行時にソースコードから機械語に変えて 実行しちゃえばいいんじゃない?
  • 14.
    © 2022 NTTDATA Corporation 14 インタープリタでの実行 def hello(name = "World") message = "Hello, " + name + ".¥n" puts message end hello() 1 2 3
  • 15.
    © 2022 NTTDATA Corporation 15 インタープリタでの実行 def hello(name = "World") message = "Hello, " + name + ".¥n" puts message end hello() 1 2 3 • プ ロ グ ラ ム 実 行 時 に ソ ー ス コ ー ド [1] を 1 文 ず つ 機械語に変換し実行する(逐次解釈) • そのためこの手法を取るとコンパイルが不要となる • 実行時に解釈する処理は重く 動作が遅くなる インタ プリタ 実行 [1]: 厳密にはインタプリタの解釈の対象はソースコードに限定されるものではありません(後述) 注: RubyにはJITコンパイルでの実行もあります
  • 16.
    © 2022 NTTDATA Corporation 16 2つのやり方とJava コンパイルして機械語を生成するやり方は 異なる環境で実行できない短所… インタープリタで逐次解釈するやり方は 動作が遅くなる短所…
  • 17.
    © 2022 NTTDATA Corporation 17 2つのやり方とJava コンパイルして機械語を生成するやり方は 異なる環境で実行できない短所… インタープリタで逐次解釈するやり方は 動作が遅くなる短所… それにJavaのこと何も話していないよ! Javaは2つの方式を合わせ持っている バイトコードは2つを合わせる手段である
  • 18.
    © 2022 NTTDATA Corporation 18 Javaコードと機械語 Javaコード • 機 械 語 に 変 え る の は 時間がかかる • テ キ ス ト フ ァ イ ル な の で どんなOSでも扱える
  • 19.
    © 2022 NTTDATA Corporation 19 Javaコードと機械語 Javaコード • 機 械 語 に 変 え る の は 時間がかかる • テ キ ス ト フ ァ イ ル な の で どんなOSでも扱える 機械語 • そ の ま ま ハ ー ド ウ ェ ア で 実行できるため 速い • 特 定 の 環 境 で の み 動作する
  • 20.
    © 2022 NTTDATA Corporation 20 Javaコードと機械語 Javaコード • 機 械 語 に 変 え る の は 時間がかかる • テ キ ス ト フ ァ イ ル な の で どんなOSでも扱える 機械語 • そ の ま ま ハ ー ド ウ ェ ア で 実行できるため 速い • 特 定 の 環 境 で の み 動作する ソースコードに比べて 機械語に変換しやすい ものにすれば 動作もある程度速く どんな環境でも利用できる 2つの中間に位置する そんなコードを作ろう!
  • 21.
    © 2022 NTTDATA Corporation 21 Javaバイトコード  コンパイル時に生成する • javacなどでのコンパイルのこと
  • 22.
    © 2022 NTTDATA Corporation 22 Javaバイトコード  コンパイル時に生成する • javacなどでのコンパイルのこと  JVM独自の表現なのでどんな環境でも利用できる • その代わりバイトコードの実行にはJVMが必要となる - JVMが多数のプラットフォームをサポートするため大きな問題ではない
  • 23.
    © 2022 NTTDATA Corporation 23 Javaプログラムを実行するということ Javaコード Javaバイトコード CAFE BABE 0000 javacなどでの コンパイル
  • 24.
    © 2022 NTTDATA Corporation 24 Javaプログラムを実行するということ Javaコード 機械語 Javaバイトコード CAFE BABE 0000 javacなどでの コンパイル JVMの インタプリタ 注: JVMにはJITコンパイルでの実行もあります
  • 25.
    © 2022 NTTDATA Corporation 25 Javaプログラムを実行するということ Javaコード 機械語 Javaバイトコード CAFE BABE 0000 javacなどでの コンパイル JVMの インタプリタ バイトコードはJavaコードよりは機械語に変換しやすいから インタプリタでの実行も向上する! バイトコードは環境に依存しなから JVMがあればどこでも動く! 注: JVMにはJITコンパイルでの実行もあります
  • 26.
    © 2022 NTTDATA Corporation 26 JVMとJavaバイトコード
  • 27.
    © 2022 NTTDATA Corporation 27 JVMでの演算  JVMは「スタック」上で演算をする • ハードウェア上ではCPUとメモリを使って処理をしているが 計算モデルとしてそう設計されている - スタックマシン
  • 28.
    © 2022 NTTDATA Corporation 28 JVMでの演算  JVMは「スタック」上で演算をする • ハードウェア上ではCPUとメモリを使って処理をしているが 計算モデルとしてそう設計されている - スタックマシン • 例: 1 + 2の演算イメージ - 1、2という数値を 順にスタックに置き +という演算を スタックから2個の数値を取って 計算し結果をスタックに置く
  • 29.
    © 2022 NTTDATA Corporation 29 JVMでの演算  JVMは「スタック」上で演算をする • もちろんハードウェア上ではCPUとメモリを使って処理をしているが 計算モデルとしてそう設計されている(スタックマシン) • 例: 1 + 2の演算イメージ - 最初スタックは空である - 1という数値を スタックに置く 1
  • 30.
    © 2022 NTTDATA Corporation 30 JVMでの演算  JVMは「スタック」上で演算をする • もちろんハードウェア上ではCPUとメモリを使って処理をしているが 計算モデルとしてそう設計されている(スタックマシン) • 例: 1 + 2の演算イメージ - 1、2という数値を 順にスタックに置く +という演算は スタックから2個の数値を取り 結果をまたスタックに置く 1 1 2 3 2 1 + - int add(int a, int b)を使う呼び出し側も同じイメージでよい * 呼び出されたaddメソッド内の処理は別途あるけれども(詳細は後述)
  • 31.
    © 2022 NTTDATA Corporation 31 JVMとJavaバイトコード  単純に言えばJavaバイトコードはJavaプログラムの内容を スタックでの処理として表現する
  • 32.
    © 2022 NTTDATA Corporation 32 JVMとJavaバイトコード  単純に言えばJavaバイトコードはJavaプログラムの内容を スタックでの処理として表現する • スタックに何かを置いたり(プッシュ) 一番上から取ったり(ポップ) インスタンスを生成したり メソッドを呼び出したり など  その表現のために使用するのがバイトコードの命令セット
  • 33.
    © 2022 NTTDATA Corporation 33 Javaバイトコードの命令セット  200種類以上ある • Java仮想マシン(JVM)仕様に定義されている - https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5
  • 34.
    © 2022 NTTDATA Corporation 34 Javaバイトコードの命令セット  200種類以上ある • Java仮想マシン(JVM)仕様に定義されている - https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5 • 命令コード(オペコード)が1バイト長 - 1バイト = 28 = 256種類までしか定義できないことになる - Javaのリリース以来 追加した命令コードは1つだけ * Project Valhallaで2つ追加する見込み
  • 35.
    © 2022 NTTDATA Corporation 35 Javaバイトコードの命令セット  200種類以上ある • Java仮想マシン(JVM)仕様に定義されている - https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-6.html#jvms-6.5 • 命令コード(オペコード)が1バイト長 - 1バイト = 28 = 256種類までしか定義できないことになる - Javaのリリース以来 追加した命令コードは1つだけ * Project Valhallaで2つ追加する見込み • 暗記する必要はとくにない - 型違い(プリミティブ型各種類と参照型で同じ内容)の命令コードも多い - スタックでの利用をイメージすると理解しやすい
  • 36.
    © 2022 NTTDATA Corporation 36 Javaバイトコードの命令コードの読み方  接頭辞の例 i... int[1] l... long f... float d... double [1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有)
  • 37.
    © 2022 NTTDATA Corporation 37 Javaバイトコードの命令セットの読み方  接頭辞の例 i... int[1] l... long f... float d... double  命令内容の例 const 定数をプッシュ add,sub,mul,div 2つポップしてそれらで 四則演算し結果をプッシュ [1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有) 接頭辞にある型と命令を組み合わせで 数が多くなっているのか 命令の例:  iconst_1: スタックに定数1をプッシュ  idiv:スタックにあるint値を2つポップして除算 結果をスタックにプッシュ  fmul: スタックにあるfloat値を2つポップして乗算 結果をスタックにプッシュ
  • 38.
    © 2022 NTTDATA Corporation 38 Javaバイトコードの命令セットの読み方  接頭辞の例 i... int[1] l... long f... float d... double  命令内容の例 const 定数をプッシュ add,sub,mul,div 2つポップしてそれらで 四則演算し結果をプッシュ [1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有) 1 + 2は 1. iconst_1 2. iconst_2 3. iadd となる 3 2 1 + 1 2 命令コードからスタックを操作する絵を描いてみると わかりやすいなあ
  • 39.
    © 2022 NTTDATA Corporation 39 Javaバイトコードの命令セットの読み方  接頭辞の例 i... int[1] l... long f... float d... double a... 参照型 *a... 1文字目の型の配列 ia...だとint配列  命令内容の例 const 定数をプッシュ load ローカル変数をプッシュ store ローカル変数に保存 return 戻り値として返す 2 プリミティブ型変換 (例: i2d) add,sub,mul,div 四則演算 neg 符号反転 [1]: intの他byte、short、char、booleanなどもJVMではintで扱います(例外有) 注: 各命令でスタックからのポップやスタックへのプッシュがあります 注: 接頭辞と命令内容のすべての組み合わせが 命令に存在するわけではありません
  • 40.
    © 2022 NTTDATA Corporation 40 メソッド処理のバイトコード表現を考えてみよう int add(int a, int b) { return a + b; } 引数aとbはどうなるの? スタックにプッシュしたい  録画再生の方は停止して30秒間考えてみましょう
  • 41.
    © 2022 NTTDATA Corporation 41 メソッド処理のバイトコード表現を考えてみよう int add(int a, int b) { return a + b; } a b iload_1はローカル変数の1番目を スタックにプッシュするのか iconst_1の_1とは違うんだ 1. メソッド開始時スタックは空 2. iload_1 • 引数はローカル変数に保存されている - インスタンスメソッドではローカル変数の 0番にthis 1番目以降に引数の値が入っている 3. iload_2 - 引数の2つ目である2番目をプッシュ a
  • 42.
    © 2022 NTTDATA Corporation 42 メソッド処理のバイトコード表現を考えてみよう int add(int a, int b) { return a + b; } a+b b a + a b 1. メソッド開始時スタックは空 2. iload_1 • 引数はローカル変数に保存されている - インスタンスメソッドではローカル変数の 0番にthis 1番目以降に引数の値 3. iload_2 4. iadd 5. ireturn a メソッドの 呼び出し元へ
  • 43.
    © 2022 NTTDATA Corporation 43 組み合わせでない命令の例  メソッド呼び出し invokestatic staticメソッド呼出 invokevirtual インスタンスメソッド invokespecial コンストラクタ、private、 スーパークラスメソッド invokeinterface インタフェースメソッド invokedynamic 動的に算出した コールサイトの呼出[1] [1]: 複雑な仕組みのためこのセッション内に理解する必要はありません invoke...はメソッド呼び出しと 捉えておけばいいか このinvokedynamicこそが 後から追加された唯一のバイトコード
  • 44.
    © 2022 NTTDATA Corporation 44 組み合わせでない命令の例  メソッド呼び出し invokestatic staticメソッド呼出 invokevirtual インスタンスメソッド  他 new,newarray インスタンス生成 pop,pop2 スタックからポップ dup... 複製をプッシュ putfield,putstatic フィールドに値をセット ldc... Constant Poolの値をプッシュ monitorenter,monitorexit ロックの確保、解放 注:すべての命令は 記載していません invokespecial コンストラクタ、private、 スーパークラスメソッド invokeinterface インタフェースメソッド invokedynamic 動的に算出した コールサイトの呼出[1] [1]: 複雑な仕組みのためこのセッション内に理解する必要はありません
  • 45.
    © 2022 NTTDATA Corporation 45 メソッド呼び出しのバイトコード表現を考えてみよう  録画再生の方は停止して30秒間考えてみましょう class Sample { int add(int a, int b) { return a + b; } } // 以下はメインメソッドの中とする Sample s = new Sample(); s.add(1, 2); 引数aとbはどうなるの? スタックにプッシュしたい
  • 46.
    © 2022 NTTDATA Corporation 46 メソッド呼び出しのバイトコード表現を考えてみよう class Sample { int add(int a, int b) { return a + b; } } // 以下はメインメソッドの中とする Sample s = new Sample(); int a = s.add(1, 2); #数字 はConstant Pool(定数プール) を番号で参照している クラス名 メソッド名なども CPに保持している 1. new #7 2. dup 3. invokespecial #9 4. astore_1 5. aload_1 6. iconst_1 7. iconst_2 8. invokevirtual #10 9. istore_2 10. return なぜdupして 複製するの?
  • 47.
    © 2022 NTTDATA Corporation 47 インスタンス生成とコンストラクタ呼び出し  JVMではこの2つは別の処理 Sample s = new Sample(); 1. new #7 2. dup 3. invokespecial #9 4. astore_1 Sam Sam Sam newでSampleインスタンスへの 参照を積む
  • 48.
    © 2022 NTTDATA Corporation 48 インスタンス生成とコンストラクタ呼び出し  JVMではこの2つは別の処理 Sample s = new Sample(); 1. new #7 2. dup 3. invokespecial #9 4. astore_1 Sam Sam Sam Sam Sam invokespecialで コンストラクタ 呼び出し Sam ローカル変数1に 保存  コンストラクタ実行 ローカル変数への保存 それぞれで参照を使用するため 複製して2つ用意した • 何かをするためにはスタックから取り出さなければならない newでSampleインスタンスへの 参照を積む
  • 49.
    © 2022 NTTDATA Corporation 49 Javaバイトコードを 実際に見てみよう
  • 50.
    © 2022 NTTDATA Corporation 50 Javaバイトコードとクラスファイル  バイトコードはクラスファイルに含まれる • クラスファイルの中を見てみよう!
  • 51.
    © 2022 NTTDATA Corporation 51 クラスファイルの中 - add()を呼び出すクラス げ、げぇー iconstとかiaddとかない どういうこと??
  • 52.
    © 2022 NTTDATA Corporation 52 Javaバイトコードとクラスファイル  バイトコードはクラスファイルに含まれる  クラスファイルはバイナリファイル • テキストファイルではない
  • 53.
    © 2022 NTTDATA Corporation 53 Javaバイトコードとクラスファイル  バイトコードはクラスファイルに含まれる  クラスファイルはバイナリファイル • テキストファイルではない • 各バイトコード命令に対応する数字がファイルに含まれる iconst_1 4 (0x4) iconst_2 5 (0x5) iadd 96 (0x60) 今まで出てきたのは バイトコードのニーモニックということか… じゃあクラスファイルを逆アセンブルすればいい?
  • 54.
    © 2022 NTTDATA Corporation 54 javapコマンド  クラスファイルを逆アセンブルするツール • https://docs.oracle.com/en/java/javase/17/docs/specs/man/javap.html • JDKに含まれる - JAVA_HOMEにパスを通していればすぐに使える
  • 55.
    © 2022 NTTDATA Corporation 55 javapコマンド  クラスファイルを逆アセンブルするツール • https://docs.oracle.com/en/java/javase/17/docs/specs/man/javap.html • JDKに含まれる - JAVA_HOMEにパスを通していればすぐに使える • -c: コードを逆アセンブル または -v: 詳細に出力 を使う • 詳細は--helpで確認できる
  • 56.
    © 2022 NTTDATA Corporation 56 javapコマンドでの逆アセンブル
  • 57.
    © 2022 NTTDATA Corporation 57 Constant Poolも見れる  javap -v の出力に含まれる
  • 58.
    © 2022 NTTDATA Corporation 58 <init>は コンストラクタのことを 指しているんだ
  • 59.
    © 2022 NTTDATA Corporation 59 バイトコードの楽しみ方
  • 60.
    © 2022 NTTDATA Corporation 60 バイトコードの楽しみ方 1. 読む! 2. 書き換える!
  • 61.
    © 2022 NTTDATA Corporation 61 バイトコードの楽しみ方 1. 読む! 2. 書き換える!
  • 62.
    © 2022 NTTDATA Corporation 62 バイトコードを読む  Javaの新バージョンリリースで新機能が出た • 新機能を使うコードを書いてバイトコードを見る
  • 63.
    © 2022 NTTDATA Corporation 63 バイトコードを読む  Javaの新バージョンリリースで新機能が出た • 新機能を使うコードを書いてバイトコードを見る  新バージョンで同じ処理でも バイトコード表現に変更があることも
  • 64.
    © 2022 NTTDATA Corporation 64 バイトコードを読む  Javaの新バージョンリリースで新機能が出た • 新機能を使うコードを書いてバイトコードを見る  新バージョンで同じ処理でも バイトコード表現に変更があることも - 例: ネストしたクラスを作り インナークラスから アウタークラスのprivateメソッドを呼び出すコード * Java 11で変わった(Nestmates/ネストメイト)
  • 65.
    © 2022 NTTDATA Corporation 65 Nestmates public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
  • 66.
    © 2022 NTTDATA Corporation 66 Java 8でコンパイルした結果をjavapで見る public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
  • 67.
    © 2022 NTTDATA Corporation 67
  • 68.
    © 2022 NTTDATA Corporation 68 Java 8でコンパイルした結果をjavapで見る  Outerにaccess$000(Outer) というstaticメソッドが!?  InnerはOuterインスタンスを生成し access$000を呼び出している public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
  • 69.
    © 2022 NTTDATA Corporation 69 Java 8でコンパイルした結果をjavapで見る  Outerにaccess$000(Outer) というstaticメソッドが!?  InnerはOuterインスタンスを 生成し access$000を 呼び出している  なぜこんなことを? • フィールドまたはメソッドRにアクセス可能 なクラスまたはインタフェースDとは、以下 のことが真となる場合のみに限る。 • Rがprivateでその宣言がDにある。 - インナークラスにこのprivateメソッドは 宣言されていないため直接呼び出せない - JVM仕様 5.5.4 https://docs.oracle.com/javase/specs/jvms/se8/html/jvms- 5.html#jvms-5.4.4 コンパイラが勝手に生成する メソッドを合成メソッドという public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
  • 70.
    © 2022 NTTDATA Corporation 70 Java 11以降でコンパイルした結果をjavapで見る public class Outer { private void m_outerpriv() { System.out.println("called m_outerpriv"); } class Inner { public void test() { new Outer().m_outerpriv(); } } public static void main(String[] args) { new Outer().new Inner().test(); } }
  • 71.
    © 2022 NTTDATA Corporation 71 Java 11以降でコンパイルした結果をjavapで見る  何が変わった? • Java 11でクラスファイルに新しい属性が追加された - ネスト関係をこの属性で把握できる $ javap -v Outer ... NestMembers: Outer$Inner $ javap -v Outer¥$Inner ... NestHost: class Outer
  • 72.
    © 2022 NTTDATA Corporation 72 Java 11以降でコンパイルした結果をjavapで見る  何が変わった? • Java 11でクラスファイルに新しい属性が追加された - ネスト関係をこの属性で把握できる $ javap -v Outer ... NestMembers: Outer$Inner $ javap -v Outer¥$Inner ... NestHost: class Outer • JVM仕様5.4.4も変更されている - R is private and is declared by a class or interface C that belongs to the same nest as D, according to the nestmate test below. * https://docs.oracle.com/javase/specs/jvms/se18/html/jvms-5.html#jvms- 5.4.4 • この変更によりprivateメソッドが 直接呼び出せるようになった 私のブログにも書いています https://www.sakatakoichi.com/entry/java11nestmates
  • 73.
    © 2022 NTTDATA Corporation 73 バイトコードの楽しみ方 1. 読む! 2. 書き換える! ぎょ、ぎょえー これを手で書き換えるの?
  • 74.
    © 2022 NTTDATA Corporation 74 バイトコード操作ライブラリ  ライブラリを使いJavaコードでバイトコード命令を書き換える • ASM https://asm.ow2.io/ • CGLIB https://github.com/cglib/cglib • Javassist https://www.javassist.org/ • Byte Buddy https://bytebuddy.net/#/ ASMはAPIの抽象度が低い Byte Buddyは抽象度が 高い
  • 75.
    © 2022 NTTDATA Corporation 75 バイトコード操作ライブラリ  ライブラリを使いJavaコードでバイトコード命令を書き換える • ASM https://asm.ow2.io/ • CGLIB https://github.com/cglib/cglib • Javassist https://www.javassist.org/ • Byte Buddy https://bytebuddy.net/#/  操作手順イメージ 1. クラスファイルを読み込む 2. Javaコードでバイトコードを書き換える(処理の追加 変更 削除) 3. 書き換えた内容を適用する - コンパイル時やビルド時はクラスファイル出力 実行時はクラスロード ASMはAPIの抽象度が低い Byte Buddyは抽象度が 高いイメージ
  • 76.
    © 2022 NTTDATA Corporation 76 ByteBuddyでの操作サンプルコード ByteBuddy byteBuddy = new ByteBuddy(); byteBuddy .redefine(TypePool.Default.of(classLoader).describe("a.b.Hello").resolve(), ClassFileLocator.ForClassLoader.of(classLoader)) .method(ElementMatchers.named("toString")) .intercept(FixedValue.value("transformed")) .make() .load(classLoader, ClassReloadingStrategy.of(inst)); // inst = java.lang.instrument.Instrumentationオブジェクト a.b.HelloクラスのtoString()メソッドを "transformed"と返すように書き換えたって感じに読める 最後はJDKのInstrument APIを使って 書き換えたクラスをロードさせて使われるようにした
  • 77.
    © 2022 NTTDATA Corporation 77 ソースコードを直接変更すればいいのでは…?  そう! でも変更したくない/できないケースもある • フレームワークやライブラリが アプリケーションで作成したクラスに 対して拡張を施したいとき - 例: Spring Hibernate • モックオブジェクトを作るとき - 例: jMockit Mockito * テストコード実行時にメソッドの内容をモック用に書き換えてしまうイメージ[1] 普段使っているフレームワークやライブラリでも バイトコードを操作しているんだ Bytecode Enhancement とも言う [1]: 新たにサブクラスを作るなどいろいろなやり方があります
  • 78.
    © 2022 NTTDATA Corporation 78 バイトコードを知って何の意味があるの?  フレームワークが使うだけなら 私たちがバイトコードを知る必要はとくにないのでは… • 明日からすぐ業務に役立つ内容ではありません • ただし技術の習得には2つの側面があります - 車輪の両輪であり 2つをバランス良く習得することが大切です 使 い 方 を 学 ぶ 仕 組 み を 学 ぶ • 使用する技術がどのように実現 されているのかを学ぶこと • 実行の仕組み 設計概念 内部的表現 • 目 的 と す る ア プ リ ケ ー シ ョ ン を 作成するためにそれをどのように 使えばよいかを学ぶこと • 言 語 構 文 FW の 設 定 ライブラリの使い方
  • 79.
    © 2022 NTTDATA Corporation 本資料に記載されている会社名、商品名、又はサービス名は、各社の登録商標又は商標です。