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

InvokeDynamic, Under the Hood

InvokeDynamic, Under the Hood

JJUG CCC 2024 Spring, 桜庭さんとの合同セッションの資料です。 #jjug_ccc

YujiSoftware

June 15, 2024
Tweet

More Decks by YujiSoftware

Other Decks in Technology

Transcript

  1. public class Foo { public static void main(String... args) {

    System.out.println("ABC" + "DEF"); } } コンパイルして、javapしてみると...
  2. 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...); }
  3. > 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 }
  4. > 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 } コンパイル時にリテラル連結
  5. > 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でメソッドコール
  6. では、これでは public class Foo { public static void main(String... args)

    { var x = "ABC"; var y = "DEF"; System.out.println(x + y); } }
  7. > 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 }
  8. invokeDynamic あとから追加された唯一のバイトコード (Java 7) メソッドコール用バイトコード invokeVirtual: インスタンスメソッド invokeStatic: クラスメソッド invokeInterface:

    インタフェース定義メソッド invokeSpecial: コンストラクタなど invokeDynamic: 動的メソッドディスパッチ どういうこと?
  9. 時はさかのぼって... 2000年代初頭: JVM言語の興隆 JRuby Jython Scala Rhino (JavaScript) Groovy et

    al. 静的型付け言語だとバイトコードに割り当てやすい 問題は動的型付けの言語
  10. Da Vince Machine Project JVMをJava以外の言語にも広げることを目的 2007年に活動開始 Project Lead: John Rose

    JSR 292: Supporting Dynamically Typed Languageon the Java Platform 2011年 invokeDynamic 導入 (Java 7)
  11. ちょっと脱線: JVM Language Summit Da Vince Machine Prj.主催 JVM特化のカンファレンス 2008年から毎年開催

    当初は JVM Language のサミット 現在は JVM と Language のサミット 2024年は 8月4日から3日間 Oracleサンタクララキャンパスで開催
  12. 検証に使用するコード Sample クラス isEven メソッド リストの中身がすべて偶数か判定するメソッド public class Sample {

    public boolean isEven(List<Integer> values) { return values.stream() .allMatch(v -> v % 2 == 0); } }
  13. javap の結果 (isEven メソッド) public boolean isEven(List<Integer>); 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 メソッドは?
  14. 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
  15. Bootstrap の処理 public final class LambdaMetafactory { public static CallSite

    metafactory(MethodHandles.Lookup AbstractValidatingLambdaMetafactory mf; mf = new InnerClassLambdaMetafactory(Objects.requireNo mf.validateMetafactoryArgs(); return mf.buildCallSite(); } インナークラスを生成するファクトリークラスが、 CallSite を生成している
  16. 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); // メソッド呼び出し
  17. 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)); }
  18. ここまでのまとめ 1. ラムダ式の処理は invokeDynamic を使っている 2. invokeDynamic の Bootstrap メソッドでは、

    直接バイトコードを組み立ててクラスを生成している 3. 生成したクラスのインスタンス生成メソッドを、 CallSite としている
  19. 成果物(Sample$$Lambda クラ ス) クラスファイルを逆コンパイルした結果 final class Sample$$Lambda implements Predicate {

    public boolean test(Object var1) { return Sample.lambda$isEven$0((Integer)var1); } } lambda$isEven$0 を呼び出している → なにこれ?
  20. 謎のメソッドの正体 改めて、検証用の Sample.class を javap してみる (プライベートメソッドも出力するため、-private を付 ける) javap

    -private Sample 結果 public class Sample { public static boolean isEven(List<Integer>); private static boolean lambda$isEven$0(Integer); }
  21. 結合処理の実装 Java 8 まで コンパイル時にコンパイラが StringBuilder を使って文字列結合する処理を作る Java 9 以降

    実行時に invokeDynamic の Bootstrap メソッドが 独自に文字列結合する処理を作る → 確認してみましょう
  22. 確認方法 適当な + 演算子で文字列結合するコードを用意 private static String makeText(int count) {

    return "Total:" + count + "files"; } Java 8 と Java 9 でコンパイル → javap してみる
  23. Java 8 の javap 結果 0: new #5 // class

    StringBuilder 3: dup 4: invokespecial #6 // Method StringBuilder."<init>":() 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
  24. Java 8 の javap 結果 (概要) StringBuilder に、ひとつづつ .append していく

    最後に .toString() で String を生成 new StringBuilder() .append("Total:") .append(count) .append("files") .toString()
  25. 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
  26. 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
  27. Java 9 の javap 結果 (概要) invokeDynamic の Bootstrap で、メソッドを作る

    Bootstrap には、テンプレート文字列を渡す 出来上がったメソッドを実行する
  28. Java 8 の実行時の動き new StringBuilder() StringBuilder を new する →

    内部でバッファ(配列)を作る byte[] { □□□□□□□□□□□□□□□□ }
  29. Java 8 の実行時の動き .toString() .toString() メソッドを呼ぶ → バッファから String にコピー

    byte[] { Total:10files□□□□□□□ } ↓↓↓↓↓↓↓↓↓↓↓↓↓ new String(□□□□□□□□□□□□□)
  30. Java 9 以降の場合 invokeDymamic を実行する → 最初に Bootstrap メソッドを呼び出す 2:

    invokedynamic #38, 0 // InvokeDynamic // #0:makeConcatWithConstants:(I)L/String;
  31. 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
  32. 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)); (中略) }
  33. Java 9 以降の場合 (after Bootstrap) invokeDymamic を実行する → 最初に Bootstrap

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

    メソッドを呼び出す → 以降、生成されたクラスのメソッドを呼び出す ↑ 気になる
  35. 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); }
  36. 生成されたメソッドの内容 // 長さの計算 long indexCoder = 11 + StringConcatHelper.LATIN1; indexCoder

    += StringConcatHelper.mix(indexCoder, var0); indexCoder は 文字列の長さ + 種類 定数 11 (= "Total:" と "files" の文字数の合計) + 種類 LATIN1 (= 0x0) / UTF16 (= 0x1_0000_0000) + 引数0 (数値) の長さ
  37. byte[] buf = StringConcatHelper.newArray("files", indexCoder); newArray でやっていること → 指定された長さのバッファ(配列)を作る →

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

    数値と文字列をバッファの指定された位置に格納 → 現在の位置を返す "Total:10" ↓↓↓↓↓↓↓↓ byte[] { ・・・・・・・・files }
  39. 実行結果の比較 Java 8 まで StringBuilder を使った処理 ちょっとずつバッファに文字列を足していく バッファの作り直しが発生する Java 9

    以降 invokeDynamic を使った処理 必要な長さを計算して、バッファを作る バッファの作り直しが発生しない 実行時の処理は、Java 9 以降の方は無駄がない