4

I'm currently looking at the bytecode that jack compiler generates for lambda expressions.

Let's take the following plain Java class as an example:

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

When using JDK 1.8's javac, it generates the following bytecode:

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

As per documentation, the translation of lambda expression to bytecode performed by generating invokedynamic call site at line 16, and by converting the body of lambda expression into a static method.

Now, let's talk about jack. Generally, when compiling lambda with minSdkVersion that is less than 24, the jack compiler generates an anonymous class - for backward compatibility with previous runtimes that are based on 1.6/1.7.

But what happen when compiling lambda with minSdkVersion set to 24?
To answer this, I created same lambda expression (as in Java sample above) and compiled it with jack in Android environment:

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));
    }
}

In this case I expected jack to generate more performant/optimized code (something similar to JVM's invokedynamic call site creating and lambda body helper method generating), since Android 7.0's runtime is based on 1.8.
I knew that invokedynamic instruction operand is not supported by neither Dalvik and ART, but thought that there will be a more optimized mechanism - otherwise lambda expression support in Android is just a syntactic sugar..

Instead, the generated bytecode included same anonymous class:

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

Why jack compiler uses the anonymous class approach to represent lambda expression in bytecode instead of more efficient one, perhaps similar to JVM's invokedynamic feature?

Alex Lipov
  • 13,503
  • 5
  • 64
  • 87
  • 2
    I also don't know why they settled on the anonymous class approach. Early on, alternatives had been considered. These included the use of experimental opcodes and some nebulous (to me) "native support". However, the supporting code got removed about 4 months ago. Cf. [here](https://android-review.googlesource.com/#/c/206561/) and [here](https://android.googlesource.com/toolchain/jack/+/b49ef949b4c97e751d73c1489513c5ece25c8afd) – Stefan Zobel Jul 31 '16 at 11:31
  • Is there any other way in Android's java to get a class very lazily resolved ? @Alex Lipov, InvokeDynamic approach of Java8 doesn't seem to work on Dalvik and Art, but is there any way to tell the compiler / JVM of android to resolve a class so that only classes needed *at runtime* are resolved when the statement using them is executed ? I think the application class is resolved this very very lazy way... http://stackoverflow.com/questions/38796215/stranger-things-in-android-class-resolution – Snicolas Aug 09 '16 at 22:50

0 Answers0