lambda       

java lambda

Lambda表达式被编译成了一个静态方法

public class StreamTest {
	public static void main(String[] args) {
    printString("hello lambda", (String s) -> System.out.println(s));
	}

public static void printString(String s, Print<String> print) {
    print.print(s);
}

@FunctionalInterface
interface Print<T> {
    public void print(T t);
}
  public com.lxs.stream.StreamTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #2                  // String hello lambda
         2: invokedynamic #3,  0              // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
         7: invokestatic  #4                  // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V
        10: return
      LineNumberTable:
        line 10: 0
        line 12: 10

  public static void printString(java.lang.String, com.lxs.stream.Print<java.lang.String>);
    descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_1
         1: aload_0
         2: invokeinterface #5,  2            // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V
         7: return
      LineNumberTable:
        line 15: 0
        line 16: 7
    Signature: #19                          // (Ljava/lang/String;Lcom/lxs/stream/Print<Ljava/lang/String;>;)V

  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     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0
         4: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 10: 0
}
SourceFile: "StreamTest.java"
InnerClasses:
     public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
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 com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
      #30 (Ljava/lang/String;)V

函数式接口

 @FunctionalInterface
 修饰符 interface 接口名称 {
    返回值类型 方法名称(可选参数信息);
    // 其他非抽象方法内容
 }

如果一个方法的参数是Lambda,那么这个参数的类型一定是函数式接口

invokedynamic

这个指令出发点是让动态语言能够在JVM上更好的运行。比如Ruby, JavaScript. 那问题是动态语言没有类型的信息,在程序运行众type可以随意变化的

invokedynamic允许程序开发人员自己来定义一个方法的链接(It allows developers to define the way how to resolve their method/linkage), JVM再来负责去优化用户的链接方法

  1. When JVM sees an invokedynamic instruction, it locates the corresponding bootstrap method in the class, and executes the bootstrap method.
  2. After executing the bootstrap method, a CallSite that is linked with a MethodHandle is returned;
  3. The invocation on the CallSite later will be transferred to real methods via a number of MethodHandles.

int x = ..
IntStream.of(1, 2, 3).map(i -> i * 2).map(i -> i * x);

Java 编译器利用 invokedynamic 指令来生成实现了函数式接口的适配器

映射方法 map 所接收的参数是 IntUnaryOperator(这是一个函数式接口)。也就是说,在运行过程中我们需要将 i->i2 和 i->ix 这两个 Lambda 表达式转化成 IntUnaryOperator 的实例。这个转化过程便是由 invokedynamic 来实现的。

在编译过程中,Java 编译器会对 Lambda 表达式进行解语法糖(desugar),生成一个方法来保存 Lambda 表达式的内容。该方法的参数列表不仅包含原本 Lambda 表达式的参数,还包含它所捕获的变量。(注:方法引用,如 Horse::race,则不会生成生成额外的方法。)

第一次执行 invokedynamic 指令时,它所对应的启动方法会通过 ASM 来生成一个适配器类。这个适配器类实现了对应的函数式接口,在我们的例子中,也就是 IntUnaryOperator。启动方法的返回值是一个 ConstantCallSite,其链接对象为一个返回适配器类实例的方法句柄。


  // i -> i * 2
  private static int lambda$0(int);
    Code:
       0: iload_0
       1: iconst_2
       2: imul
       3: ireturn

  // i -> i * x
  private static int lambda$1(int, int);
    Code:
       0: iload_1
       1: iload_0
       2: imul
       3: ireturn
         
         
  
// i->i*2 对应的适配器类
final class LambdaTest$$Lambda$1 implements IntUnaryOperator {
 private LambdaTest$$Lambda$1();
  Code:
    0: aload_0
    1: invokespecial java/lang/Object."<init>":()V
    4: return

 public int applyAsInt(int);
  Code:
    0: iload_1
    1: invokestatic LambdaTest.lambda$0:(I)I
    4: ireturn
}

// i->i*x 对应的适配器类
final class LambdaTest$$Lambda$2 implements IntUnaryOperator {
 private final int arg$1;

 private LambdaTest$$Lambda$2(int);
  Code:
    0: aload_0
    1: invokespecial java/lang/Object."<init>":()V
    4: aload_0
    5: iload_1
    6: putfield arg$1:I
    9: return

 private static java.util.function.IntUnaryOperator get$Lambda(int);
  Code:
    0: new LambdaTest$$Lambda$2
    3: dup
    4: iload_0
    5: invokespecial "<init>":(I)V
    8: areturn

 public int applyAsInt(int);
  Code:
    0: aload_0
    1: getfield arg$1:I
    4: iload_1
    5: invokestatic LambdaTest.lambda$1:(II)I
    8: ireturn
}

可以看到,捕获了局部变量的 Lambda 表达式多出了一个 get$Lambda 的方法。启动方法便会所返回的调用点链接至指向该方法的方法句柄。也就是说,每次执行 invokedynamic 指令时,都会调用至这个方法中,并构造一个新的适配器类实例。

invokedymaic 指令抽象出调用点的概念,并且将调用该调用点所链接的方法句柄。在第一次执行 invokedynamic 指令时,Java 虚拟机将执行它所对应的启动方法,生成并且绑定一个调用点。之后如果再次执行该指令,Java 虚拟机则直接调用已经绑定了的调用点所链接的方法。

Lambda 表达式到函数式接口的转换是通过 invokedynamic 指令来实现的。该 invokedynamic 指令对应的启动方法将通过 ASM 生成一个适配器类。对于没有捕获其他变量的 Lambda 表达式,该 invokedynamic 指令始终返回同一个适配器类的实例。

对于捕获了其他变量的 Lambda 表达式,每次执行 invokedynamic 指令将新建一个适配器类实例。

不管是捕获型的还是未捕获型的 Lambda 表达式,它们的性能上限皆可以达到直接调用的性能。其中,捕获型 Lambda 表达式借助了即时编译器中的逃逸分析,来避免实际的新建适配器类实例的操作。

缓存方法句柄


// 需要更改ASMHelper.MyMethodVisitor中的BOOTSTRAP_CLASS_NAME
import java.lang.invoke.*;

public class MonomorphicInlineCache {

  private final MethodHandles.Lookup lookup;
  private final String name;

  public MonomorphicInlineCache(MethodHandles.Lookup lookup, String name) {
    this.lookup = lookup;
    this.name = name;
  }

  private Class<?> cachedClass = null;
  private MethodHandle mh = null;

  public void invoke(Object receiver) throws Throwable {
    if (cachedClass != receiver.getClass()) {
      cachedClass = receiver.getClass();
      mh = lookup.findVirtual(cachedClass, name, MethodType.methodType(void.class));
    }
    mh.invoke(receiver);
  }

  public static CallSite bootstrap(MethodHandles.Lookup l, String name, MethodType callSiteType) throws Throwable {
    MonomorphicInlineCache ic = new MonomorphicInlineCache(l, name);
    MethodHandle mh = l.findVirtual(MonomorphicInlineCache.class, "invoke", MethodType.methodType(void.class, Object.class));
    return new ConstantCallSite(mh.bindTo(ic));
  }
}

参考

https://time.geekbang.org/column/article/12574

https://zhuanlan.zhihu.com/p/26389041

https://zhuanlan.zhihu.com/p/30936412

https://blog.csdn.net/hj7jay/article/details/73480386?ivk_sa=1024320u