Slide 1

Slide 1 text

InvokeDynamic, Under the Hood YujiSoftware Yuichi Sakuraba

Slide 2

Slide 2 text

javap してますか?

Slide 3

Slide 3 text

javap クラスファイル解析ツール クラスやメソッドの情報抽出 バイトコード解析

Slide 4

Slide 4 text

public class Foo { public static void main(String... args) { System.out.println("ABC" + "DEF"); } } コンパイルして、javapしてみると...

Slide 5

Slide 5 text

public class Foo { public static void main(String... args) { System.out.println("ABC" + "DEF"); } } コンパイルして、javapしてみると... > javac Foo.java > javap Foo Compiled from "Foo.java" public class Foo { public Foo(); public static void main(java.lang.String...); }

Slide 6

Slide 6 text

> javap -c Foo -c: 逆コンパイルオプション Compiled from "Foo.java" public class Foo { public Foo(); Code: 0: aload_0 1: invokespecial #1 // Method "init":()V 4: return public static void main(java.lang.String...); Code: 0: getstatic #7 // Field System.out 3: ldc #13 // String ABCDEF 5: invokevirtual #15 // Method println:(LString;)V 8: return }

Slide 7

Slide 7 text

> javap -c Foo -c: 逆コンパイルオプション Compiled from "Foo.java" public class Foo { public Foo(); Code: 0: aload_0 1: invokespecial #1 // Method "init":()V 4: return public static void main(java.lang.String...); Code: 0: getstatic #7 3: ldc #13 // String ABCDEF 5: invokevirtual #15 // Method println:(LString;)V 8: return } コンパイル時にリテラル連結

Slide 8

Slide 8 text

> javap -c Foo -c: 逆コンパイルオプション Compiled from "Foo.java" public class Foo { public Foo(); Code: 0: aload_0 1: invokespecial #1 // Method "init":()V 4: return public static void main(java.lang.String...); Code: 0: getstatic #7 // Field System.out 3: ldc #13 // String ABCDEF 5: invokevirtual #15 // Method println:(LString;)V 8: return } invokeXXXでメソッドコール

Slide 9

Slide 9 text

では、これでは public class Foo { public static void main(String... args) { var x = "ABC"; var y = "DEF"; System.out.println(x + y); } }

Slide 10

Slide 10 text

> javap -c Foo ... public static void main(java.lang.String...); Code: 0: ldc #7 // String ABC 2: astore_1 3: ldc #7 // String DEF 5: astore_2 6: getstatic #9 // Field System.out 9: aload_1 10: aload_2 11: invokedynamic #15, 0 16: invokevirtual #19 // Method println:(LString;)V 19: return }

Slide 11

Slide 11 text

メソッドコールをしていないのに invokeで始まるバイトコード???

Slide 12

Slide 12 text

メソッドコールをしていないのに invokeで始まるバイトコード??? 本日の主役: invokeDynamic

Slide 13

Slide 13 text

invokeDynamic あとから追加された唯一のバイトコード (Java 7)

Slide 14

Slide 14 text

invokeDynamic あとから追加された唯一のバイトコード (Java 7) メソッドコール用バイトコード invokeVirtual: インスタンスメソッド invokeStatic: クラスメソッド invokeInterface: インタフェース定義メソッド invokeSpecial: コンストラクタなど

Slide 15

Slide 15 text

invokeDynamic あとから追加された唯一のバイトコード (Java 7) メソッドコール用バイトコード invokeVirtual: インスタンスメソッド invokeStatic: クラスメソッド invokeInterface: インタフェース定義メソッド invokeSpecial: コンストラクタなど invokeDynamic: 動的メソッドディスパッチ

Slide 16

Slide 16 text

invokeDynamic あとから追加された唯一のバイトコード (Java 7) メソッドコール用バイトコード invokeVirtual: インスタンスメソッド invokeStatic: クラスメソッド invokeInterface: インタフェース定義メソッド invokeSpecial: コンストラクタなど invokeDynamic: 動的メソッドディスパッチ どういうこと?

Slide 17

Slide 17 text

時はさかのぼって... 2000年代初頭: JVM言語の興隆

Slide 18

Slide 18 text

時はさかのぼって... 2000年代初頭: JVM言語の興隆 JRuby Jython Scala Rhino (JavaScript) Groovy et al.

Slide 19

Slide 19 text

時はさかのぼって... 2000年代初頭: JVM言語の興隆 JRuby Jython Scala Rhino (JavaScript) Groovy et al. 静的型付け言語だとバイトコードに割り当てやすい 問題は動的型付けの言語

Slide 20

Slide 20 text

動的型付け言語におけるメソッドコール 型が実行時にならないと決まらない コールするメソッドも実行時にならないと決まらない x.do(a, b);

Slide 21

Slide 21 text

動的型付け言語におけるメソッドコール 型が実行時にならないと決まらない コールするメソッドも実行時にならないと決まらない Foo.do(int a, int b) {...} x.do(a, b); xはFoo? Bar? Bar.do(String a, String b) {...}

Slide 22

Slide 22 text

動的型付け言語におけるメソッドコール 型が実行時にならないと決まらない コールするメソッドも実行時にならないと決まらない Foo.do(int a, int b) {...} x.do(a, b); xはFoo? Bar? Bar.do(String a, String b) {...} 実行時にコールするメソッドを決定

Slide 23

Slide 23 text

動的型付け言語におけるメソッドコール 型が実行時にならないと決まらない コールするメソッドも実行時にならないと決まらない Foo.do(int a, int b) {...} x.do(a, b); xはFoo? Bar? Bar.do(String a, String b) {...} 実行時にコールするメソッドを決定 動的メソッドディスパッチ

Slide 24

Slide 24 text

そこで動いたのが...

Slide 25

Slide 25 text

そこで動いたのが... JRuby作者 Charles Nutter

Slide 26

Slide 26 text

そこで動いたのが... JRuby作者 Charles Nutter John Rose (Sun, 現Oracle)

Slide 27

Slide 27 text

Da Vince Machine Project JVMをJava以外の言語にも広げることを目的 2007年に活動開始

Slide 28

Slide 28 text

Da Vince Machine Project JVMをJava以外の言語にも広げることを目的 2007年に活動開始 Project Lead: John Rose

Slide 29

Slide 29 text

Da Vince Machine Project JVMをJava以外の言語にも広げることを目的 2007年に活動開始 Project Lead: John Rose JSR 292: Supporting Dynamically Typed Languageon the Java Platform 2011年 invokeDynamic 導入 (Java 7)

Slide 30

Slide 30 text

ちょっと脱線: JVM Language Summit Da Vince Machine Prj.主催 JVM特化のカンファレンス 2008年から毎年開催 当初は JVM Language のサミット 現在は JVM と Language のサミット

Slide 31

Slide 31 text

ちょっと脱線: JVM Language Summit Da Vince Machine Prj.主催 JVM特化のカンファレンス 2008年から毎年開催 当初は JVM Language のサミット 現在は JVM と Language のサミット 2024年は 8月4日から3日間 Oracleサンタクララキャンパスで開催

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

invokeDynamic の動作

Slide 36

Slide 36 text

invoke ↓ メソッドを実行する

Slide 37

Slide 37 text

dynamic ↓ 動的に

Slide 38

Slide 38 text

invokeDynamic とは 対象のメソッドを動的に決定して、実行する

Slide 39

Slide 39 text

invokeDynamic とは 対象のメソッドを動的に決定して、実行する ↓ どうやって決定する?

Slide 40

Slide 40 text

invokeDynamic の処理の流れ Bootstrapメソッド … 実行するメソッドを決定する処理 Lookupクラス … メソッドを検索するためのクラス CallSiteクラス … 実行対象のMethodHandleを保持 MethodHandleクラス … メソッドの参照

Slide 41

Slide 41 text

invokeDynamic のポイント 初回のメソッド実行前に Bootstarap を実行し、 任意の処理をして CallSite を返す

Slide 42

Slide 42 text

invokeDynamic のポイント 初回のメソッド実行前に Bootstarap を実行し、 任意の処理をして CallSite を返す ↓ 動的メソッド検索以外のことをしても構わない

Slide 43

Slide 43 text

invokeDynamic のポイント 初回のメソッド実行前に Bootstarap を実行し、 任意の処理をして CallSite を返す ↓ 動的メソッド検索以外のことをしても構わない ↓ Bootstrap でメソッドを作って返すこともできる

Slide 44

Slide 44 text

invokeDynamic のポイント 初回のメソッド実行前に Bootstarap を実行し、 任意の処理をして CallSite を返す ↓ 動的メソッド検索以外のことをしても構わない ↓ Bootstrap でメソッドを作って返すこともできる ↓ Java でも使いたい!

Slide 45

Slide 45 text

Java と invokeDynamic Java のいろいろなところで invokeDynamic が使われている! ラムダ式 文字列結合 レコードクラス switch 式/文のパターン・マッチング (Java 22 時点)

Slide 46

Slide 46 text

ラムダ式と invokeDynamic

Slide 47

Slide 47 text

ラムダ式と invokeDynamic ラムダ式とは 関数を簡単に書けるようにしたもの 実体は、 関数型インタフェースの実装クラスを new すること これと invokeDynamic になんの関係が…?

Slide 48

Slide 48 text

ラムダ式と invokeDynamic ラムダ式とは 関数を簡単に書けるようにしたもの 実体は、 関数型インタフェースの実装クラスを new すること ↑ これを用意するのに invokeDynamic を使っている

Slide 49

Slide 49 text

実際に確認してみましょう!

Slide 50

Slide 50 text

検証に使用するコード Sample クラス isEven メソッド リストの中身がすべて偶数か判定するメソッド public class Sample { public boolean isEven(List values) { return values.stream() .allMatch(v -> v % 2 == 0); } }

Slide 51

Slide 51 text

確認手順 1. javac でコンパイル 2. javap でリバースアセンブルする (詳細を出力するため、-verbose をつける) javac Sample.java javap -verbose Sample.class → いろいろなことがわかる

Slide 52

Slide 52 text

javap -verbose でわかること クラスファイルの中身が分かる クラス定義・定数プール・フィールド定義 メソッド 定義 バイトコード 属性 invokeDynamic で呼び出す Bootstrap メソッド レコードクラスの定義 etc...

Slide 53

Slide 53 text

javap の結果 (isEven メソッド) public boolean isEven(List); 0: aload_1 1: invokeinterface #7, 1 // InterfaceMethod List.stream:()L/Stream; 6: invokedynamic #13, 0 // InvokeDynamic #0:test:()L/Predicate; 11: invokeinterface #17, 2 // InterfaceMethod Stream.allMatch:(L/Predicate; 16: ireturn invokeDynamic が使われている! → Bootstrap メソッドは?

Slide 54

Slide 54 text

javap の結果 (Bootstrap) BootstrapMethods: 0: #49 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory: ( L/MethodHandles$Lookup; L/String; L/MethodType; L/MethodType; L/MethodHandle; L/MethodType; )L/CallSite; Method arguments: #43 (L/Object;)Z #45 REF_invokeStatic Lambda.lambda$isEven$0:(L/Inte #48 (L/Integer;)Z

Slide 55

Slide 55 text

Bootstrap の処理 public final class LambdaMetafactory { public static CallSite metafactory(MethodHandles.Lookup AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(Objects.requireNo mf.validateMetafactoryArgs(); return mf.buildCallSite(); } インナークラスを生成するファクトリークラスが、 CallSite を生成している

Slide 56

Slide 56 text

InnerClassLambdaMetafactory ASM(Java バイトコード操作フレームワーク)を使い、 直接バイトコードを組み立ててクラスを作っている MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, constructorType.toMethodDescriptorString(), null, null); // メソッド定義 ctor.visitCode(); // コードブロック開始 ctor.visitVarInsn(ALOAD, 0); // 値のロード ctor.visitMethodInsn( INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR, METHOD_DESCRIPTOR_VOID, false); // メソッド呼び出し

Slide 57

Slide 57 text

buildCallSite() の処理 生成したクラスのインスタンス生成メソッドを CallSite として返す if (factoryType.parameterCount() == 0) { // In the case of a non-capturing lambda, we // optimize linkage by pre-computing a single instance Object inst = mh.asType(methodType(Object.class)) .invokeExact(); return new ConstantCallSite( MethodHandles.constant(interfaceClass, inst)); } else { return new ConstantCallSite(mh.asType(factoryType)); }

Slide 58

Slide 58 text

ここまでのまとめ 1. ラムダ式の処理は invokeDynamic を使っている 2. invokeDynamic の Bootstrap メソッドでは、 直接バイトコードを組み立ててクラスを生成している 3. 生成したクラスのインスタンス生成メソッドを、 CallSite としている

Slide 59

Slide 59 text

クラスの中身、気になります!

Slide 60

Slide 60 text

Bootstrap の成果物 ラムダの Bootstrap メソッドで使用している InnerClassLambdaMetafactory クラスには 生成したクラスの出力機能がある

Slide 61

Slide 61 text

生成したクラスの出力機能 システムプロパティを指定して実行する -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles DUMP_LAMBDA_PROXY_CLASS_FILES という フォルダにクラスファイルができる これで Bootstrap の成果物であるクラスがわかる!

Slide 62

Slide 62 text

成果物(Sample$$Lambda クラ ス) クラスファイルを逆コンパイルした結果 final class Sample$$Lambda implements Predicate { public boolean test(Object var1) { return Sample.lambda$isEven$0((Integer)var1); } } lambda$isEven$0 を呼び出している → なにこれ?

Slide 63

Slide 63 text

謎のメソッドの正体 改めて、検証用の Sample.class を javap してみる (プライベートメソッドも出力するため、-private を付 ける) javap -private Sample

Slide 64

Slide 64 text

謎のメソッドの正体 改めて、検証用の Sample.class を javap してみる (プライベートメソッドも出力するため、-private を付 ける) javap -private Sample 結果 public class Sample { public static boolean isEven(List); private static boolean lambda$isEven$0(Integer); }

Slide 65

Slide 65 text

lambda$isEven$0 メソッドの正体 メソッドを逆コンパイルした結果 private static boolean lambda$isEven$0(Integer v) { return v % 2 == 0 } → ラムダ式の中身が private メソッドになっている

Slide 66

Slide 66 text

最終的な処理の流れ

Slide 67

Slide 67 text

文字列結合と invokeDynamic

Slide 68

Slide 68 text

Java での文字列結合 Java では + 演算子で文字列結合ができる この結合処理の実装が、バージョンアップで変わった (JEP 280: Indify String Concatenation)

Slide 69

Slide 69 text

結合処理の実装 Java 8 まで コンパイル時にコンパイラが StringBuilder を使って文字列結合する処理を作る Java 9 以降 実行時に invokeDynamic の Bootstrap メソッドが 独自に文字列結合する処理を作る → 確認してみましょう

Slide 70

Slide 70 text

確認方法 適当な + 演算子で文字列結合するコードを用意 private static String makeText(int count) { return "Total:" + count + "files"; } Java 8 と Java 9 でコンパイル → javap してみる

Slide 71

Slide 71 text

Java 8 の javap 結果 0: new #5 // class StringBuilder 3: dup 4: invokespecial #6 // Method StringBuilder."":() 7: ldc #7 // String Total: 9: invokevirtual #8 // Method StringBuilder.append:(LSt 12: iload_0 13: invokevirtual #9 // Method StringBuilder.append:(I)L 16: ldc #10 // String files 18: invokevirtual #8 // Method StringBuilder.append:(LSt 21: invokevirtual #11 // Method StringBuilder.toString:() 24: areturn

Slide 72

Slide 72 text

Java 8 の javap 結果 (概要) StringBuilder に、ひとつづつ .append していく 最後に .toString() で String を生成 new StringBuilder() .append("Total:") .append(count) .append("files") .toString()

Slide 73

Slide 73 text

Java 9 の javap 結果 1: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants: (I)Ljava/lang/String; ------- BootstrapMethods: 0: #25 REF_invokeStatic java/lang/invoke/StringConcatFactory .makeConcatWithConstants: ( LMethodHandles$Lookup;LString;LMethodType;LString;[L )LCallSite; Method arguments: #26 Total:\u0001files

Slide 74

Slide 74 text

Java 9 の javap 結果 1: invokedynamic #5, 0 // InvokeDynamic #0:makeConcatWithConstants: (I)Ljava/lang/String; ------- BootstrapMethods: 0: #25 REF_invokeStatic java/lang/invoke/StringConcatFactory .makeConcatWithConstants: ( LMethodHandles$Lookup;LString;LMethodType;LString;[L )LCallSite; Method arguments: #26 Total:\u0001files

Slide 75

Slide 75 text

Java 9 の javap 結果 (概要) invokeDynamic の Bootstrap で、メソッドを作る Bootstrap には、テンプレート文字列を渡す 出来上がったメソッドを実行する

Slide 76

Slide 76 text

Java 8 と Java 9 で 実行した時の動きを追ってみましょう

Slide 77

Slide 77 text

Java 8 の実行時の動き new StringBuilder() StringBuilder を new する → 内部でバッファ(配列)を作る byte[] { □□□□□□□□□□□□□□□□ }

Slide 78

Slide 78 text

Java 8 の実行時の動き .append("Total:") 文字列を引数に .append() メソッドを呼ぶ → 文字列をバッファ(配列)に格納 "Total:" ↓↓↓↓↓↓ byte[] { □□□□□□□□□□ }

Slide 79

Slide 79 text

Java 8 の実行時の動き .append(10) 数値を引数に .append() メソッドを呼ぶ → 数値を文字列に変換して、バッファ(配列)に格納 10 → "10" ↓↓ byte[] { Total:□□□□ }

Slide 80

Slide 80 text

Java 8 の実行時の動き .append("files") 文字列を引数に .append() メソッドを呼ぶ → バッファ(配列)に …、空きが足りない "files" ↓↓↓↓↓ byte[] { Total:10□□ }

Slide 81

Slide 81 text

Java 8 の実行時の動き → 新しいバッファを作り、内容をコピー byte[] { Total:10□□ } ↓↓↓↓↓↓↓↓↓↓ byte[] { □□□□□□□□□□□□□□□□□□□□ }

Slide 82

Slide 82 text

Java 8 の実行時の動き → バッファ(配列)に文字列を格納 "files" ↓↓↓↓↓ byte[] { Total:10□□□□□□□□□□□□ }

Slide 83

Slide 83 text

Java 8 の実行時の動き .toString() .toString() メソッドを呼ぶ → バッファから String にコピー byte[] { Total:10files□□□□□□□ } ↓↓↓↓↓↓↓↓↓↓↓↓↓ new String(□□□□□□□□□□□□□)

Slide 84

Slide 84 text

Java 8 の実行時の動きまとめ バッファ(配列)に文字列を足していく バッファが足りなくなったら作り直す バッファをコピーして String を作る

Slide 85

Slide 85 text

Java 9 以降の場合 invokeDymamic を実行する → 最初に Bootstrap メソッドを呼び出す 2: invokedynamic #38, 0 // InvokeDynamic // #0:makeConcatWithConstants:(I)L/String;

Slide 86

Slide 86 text

Java 9 以降の場合 (Bootstrap) 「Total:\u0001files」を 引数に StringConcatFactory.makeConcatWithConstants を呼 ぶ BootstrapMethods: 0: #25 REF_invokeStatic java/lang/invoke/StringConcatFactory .makeConcatWithConstants: ( LMethodHandles$Lookup;LString;LMethodType;LString;[L )LCallSite; Method arguments: #26 Total:\u0001files

Slide 87

Slide 87 text

Java 9 以降の場合 (in Bootstrap) Boostrap は、文字列を結合するメソッドを作って返す public static CallSite makeConcatWithConstants( MethodHandles.Lookup lookup, String name, MethodType concatType, String recipe, Object... constants ) throws StringConcatException { (中略) return new ConstantCallSite( generateMHInlineCopy(concatType, constantStri .viewAsType(concatType, true)); (中略) }

Slide 88

Slide 88 text

Java 9 以降の場合 (in Bootstrap) Boostrap は、文字列を結合するメソッドを作って返す 生成するメソッドの内容 文字列全体の長さを計算 バッファを作成 文字列を当てはめる String にする

Slide 89

Slide 89 text

Java 9 以降の場合 (after Bootstrap) invokeDymamic を実行する → 最初に Bootstrap メソッドを呼び出す → 以降、生成されたクラスのメソッドを呼び出す

Slide 90

Slide 90 text

Java 9 以降の場合 (after Bootstrap) invokeDymamic を実行する → 最初に Bootstrap メソッドを呼び出す → 以降、生成されたクラスのメソッドを呼び出す ↑ 気になる

Slide 91

Slide 91 text

生成されたメソッドの詳細 文字列結合の Bootstrap メソッドには 生成したメソッドを出力する機能がない → どうするか

Slide 92

Slide 92 text

生成されたメソッドの詳細 文字列結合の Bootstrap メソッドには 生成したメソッドを出力する機能がない → どうするか → メソッド生成処理を読み解けばいい!

Slide 93

Slide 93 text

String LambdaForm$MH/0x00007a15ef006000(int var0) { // 長さの計算 long indexCoder = 11 + StringConcatHelper.LATIN1; indexCoder += StringConcatHelper.mix(indexCoder, var0); // バッファ(配列)を作って文字列を格納 byte[] buf = StringConcatHelper.newArray("files", indexCoder); indexCoder = StringConcatHelper.prepend(indexCoder, buf, var1, "Tot // String にする return new String(buf); }

Slide 94

Slide 94 text

生成されたメソッドのシグネチャ static String LambdaForm$MH/0x00007a15ef006000(int var0) 定数以外の部分を引数で受けとり、文字列を返すメソッド → 今回は int 型の引数を1つ取る

Slide 95

Slide 95 text

生成されたメソッドの内容 // 長さの計算 long indexCoder = 11 + StringConcatHelper.LATIN1; indexCoder += StringConcatHelper.mix(indexCoder, var0); indexCoder は 文字列の長さ + 種類 定数 11 (= "Total:" と "files" の文字数の合計) + 種類 LATIN1 (= 0x0) / UTF16 (= 0x1_0000_0000) + 引数0 (数値) の長さ

Slide 96

Slide 96 text

byte[] buf = StringConcatHelper.newArray("files", indexCoder); newArray でやっていること → 指定された長さのバッファ(配列)を作る → 文字列をバッファに格納 → 現在の位置を返す "files" ↓↓↓↓↓ byte[] { ・・・・・・・・・・・・・ }

Slide 97

Slide 97 text

indexCoder = StringConcatHelper.prepend( indexCoder, buf, var0, "Total:"); prepend でやっていること → 数値と文字列をバッファの指定された位置に格納 → 現在の位置を返す "Total:10" ↓↓↓↓↓↓↓↓ byte[] { ・・・・・・・・files }

Slide 98

Slide 98 text

return new String(buf); String を作る バッファはコピーせずにそのまま使う byte[] { Total:10files } ↓ String(value, coder);

Slide 99

Slide 99 text

実行結果の比較 Java 8 まで StringBuilder を使った処理 ちょっとずつバッファに文字列を足していく バッファの作り直しが発生する Java 9 以降 invokeDynamic を使った処理 必要な長さを計算して、バッファを作る バッファの作り直しが発生しない 実行時の処理は、Java 9 以降の方は無駄がない

Slide 100

Slide 100 text

invokeDynamic の可能性 invokeDynamic を使えば実行時に処理を差し込める コンパイル時にやるようなことを実行時に行える 最適な処理を生成しやすい さらに、まだ Java で使っていない機能もある CallSite の MethodHandle の更新 Java でも動的ディスパッチしたい場合がでてきたら 使うかも!?

Slide 101

Slide 101 text

まとめ もともと invokeDynamic は JVM で動的型付け言語を 効率的に処理するために実装された 現在は、Java でも使われている Lambda の実装 文字列結合の処理生成 など

Slide 102

Slide 102 text

まとめ もともと invokeDynamic は JVM で動的型付け言語を 効率的に処理するために実装された 現在は、Java でも使われている Lambda の実装 文字列結合の処理生成 など 今後はさらに使われるようになるかも!

Slide 103

Slide 103 text

InvokeDynamic, Under the Hood YujiSoftware Yuichi Sakuraba