4

私は現在、jackコンパイラがラムダ式のために生成するバイトコードを見ています。

例として、次のプレーン Java クラスを見てみましょう。

public class ForEach {
    public static void main (String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.forEach(s -> System.out.println(s));
    }
}

JDK 1.8 を使用javacすると、次のバイトコードが生成されます。

public class ForEach
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
{

// .. redacted

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/util/ArrayList
         3: dup
         4: invokespecial #3                  // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #4                  // String hello
        11: invokevirtual #5                  // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        14: pop
        15: aload_1
        16: invokedynamic #6,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
        21: invokevirtual #7                  // Method java/util/ArrayList.forEach:(Ljava/util/function/Consumer;)V
        24: return

  private static void lambda$main$0(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 7: 0
}
BootstrapMethods:
  0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #28 (Ljava/lang/Object;)V
      #29 invokestatic ForEach.lambda$main$0:(Ljava/lang/String;)V
      #30 (Ljava/lang/String;)V

ドキュメントに従って、ラムダ式のバイトコードへのinvokedynamic call site変換は、16 行目で生成し、ラムダ式の本体を静的メソッドに変換することによって実行されます。

では、についてお話しましょうjack。一般に、ラムダをminSdkVersion24 未満でコンパイルすると、jackコンパイラは匿名クラスを生成します。これは、1.6/1.7 に基づく以前のランタイムとの後方互換性のためです。

しかし、ラムダminSdkVersionを 24 に設定してコンパイルするとどうなるでしょうか?
これに答えるために、(上記の Java サンプルと同様に) 同じラムダ式を作成しjack、Android 環境でコンパイルしました。

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ArrayList<String> list = new ArrayList<>();
        list.add("hello");
        list.forEach(s -> System.out.println(s));
    }
}

この場合、Android 7.0 のランタイムは 1.8 に基づいているため、jackより効率的で最適化されたコード (JVM の作成およびラムダ ボディ ヘルパー メソッドの生成に似たもの) を生成することを期待していました。 命令オペランドが Dalvik と ART のどちらでもサポートされていないことはわかっていましたが、より最適化されたメカニズムがあると考えていました。それ以外の場合、Android でのラムダ式のサポートは単なる構文糖衣です。 invokedynamic call site
invokedynamic

代わりに、生成されたバイトコードには同じ匿名クラスが含まれていました。

Class #48            -
  Class descriptor  : 'Linfo/osom/java8demo/MainActivity;'

// .. redacted

  Virtual methods   -
    #0              : (in Linfo/osom/java8demo/MainActivity;)
      name          : 'onCreate'
      type          : '(Landroid/os/Bundle;)V'
      access        : 0x0004 (PROTECTED)
[0014a0] info.osom.java8demo.MainActivity.onCreate:(Landroid/os/Bundle;)V
0000: invoke-super {v2, v3}, Landroid/app/Activity;.onCreate:(Landroid/os/Bundle;)V // method@0001
0003: const/high16 v0, #int 2130903040 // #7f03
0005: invoke-virtual {v2, v0}, Linfo/osom/java8demo/MainActivity;.setContentView:(I)V // method@0020
0008: new-instance v0, Ljava/util/ArrayList; // type@005b
000a: invoke-direct {v0}, Ljava/util/ArrayList;.<init>:()V // method@003f
000d: const-string/jumbo v1, "hello" // string@000000b9
0010: invoke-virtual {v0, v1}, Ljava/util/ArrayList;.add:(Ljava/lang/Object;)Z // method@0041
0013: new-instance v1, Linfo/osom/java8demo/MainActivity$-void_onCreate_android_os_Bundle_savedInstanceState_LambdaImpl0; // type@003c
0015: invoke-direct {v1}, Linfo/osom/java8demo/MainActivity$-void_onCreate_android_os_Bundle_savedInstanceState_LambdaImpl0;.<init>:()V // method@001b
0018: invoke-virtual {v0, v1}, Ljava/util/ArrayList;.forEach:(Ljava/util/function/Consumer;)V // method@0042
001b: return-void

// .. redacted

おそらくJVMの機能jackに似た、より効率的なものではなく、コンパイラがバイトコードでラムダ式を表すために匿名クラスアプローチを使用するのはなぜですか?invokedynamic

4

0 に答える 0