java &d8中Lambda的实现原理

基础概念

lambda表达式

lambda是java8加入的语法糖,它是一个匿名函数,即没有声明的方法,它无访问修饰符、无返回值声明和方法名称。语法格式如下:

(arg1, arg2...) -> { body}
  • 当只有一个参数且不声明类型时,可以省略参数的括号。
  • 当body部分只有一个语句时,可以省略大括号。
  • 参数类型可以显示声明,也可以由编译器自动从上下文推倒。如(String s) -> System.out.println(s)

函数式接口

在 Java 中,函数式接口(Functional interface)指只有一个抽象方法的接口。可以添加@FunctionalInterface注解明确设计意图,如果接口中的方法多于一个时,编译时就会产生错误。

lambda表达式可以隐式的分配给功能接口。如Runnable接口的创建,可以写成如下形式:

Runnable r = () -> System.out.println("hello lambda");

又如:

new Thread(
    () -> System.out.println("hello runnable");
)

java8 lambda的实现

在JDK,lambda表达式这里使用了invokedynamic指令,它是java 7引入的新虚拟机指令,也是Java1.0以来第一次引入的新的指令,在java 8中开始正式使用。它与其他invoke指令(invokevirutal,invokestatic, invokeinterface,invokespecial)最大的区别在于它在运行时通过Bootstrap Method简称(BSM)机制动态确定方法所属者和类型。第一次运行时BSM会返回一个调用点(CallSite)对象,并和invokedynamic指令链接,后续调用不再生成CallSite对象和链接操作。

下面我们通过以下示例来看一下java8 中用invokedynamic实现lambda的具体原理。

测试demo

//Java8.java
class Java8 {
    interface Logger {
        void log(String s);
    }

    public static void main(String... args) {
        myPrint(s->System.out.println(s));
    }

    private static void myPrint(Logger logger) {
        logger.log("hello lambda");
    }
}

执行:

javac Java8.java
javap -v -p Java8.class

输出如下:

Classfile /Users/wuzhentang/workspace/java/desugared/Java8.class
  Last modified 2021-4-5; size 1097 bytes
  MD5 checksum 0314827cc5a7d10c76a7d9907982c371
  Compiled from "java8.java"
class Java8
  ...
Constant pool:
   #1 = Methodref          #9.#25         // java/lang/Object."":()V
 .....
  #26 = Utf8               BootstrapMethods
  #27 = MethodHandle       #6:#41         // 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;
  .....
  #49 = Utf8               println
  #50 = Class              #53            // java/lang/invoke/LambdaMetafactory
  #51 = NameAndType        #54:#57        // 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;
  #52 = NameAndType        #21:#22        // lambda$main$0:(Ljava/lang/String;)V
  #53 = Utf8               java/lang/invoke/LambdaMetafactory
  #54 = Utf8               metafactory
  #55 = Class              #59            // java/lang/invoke/MethodHandles$Lookup
  #56 = Utf8               Lookup
  #57 = Utf8               (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;
  #58 = Class              #60            // java/lang/invoke/MethodHandles
  #59 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #60 = Utf8               java/lang/invoke/MethodHandles
{
  Java8();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 2: 0

  public static void main(java.lang.String...);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS
    Code:
      stack=1, locals=1, args_size=1
         0: invokedynamic #2,  0    // InvokeDynamic #0:log:()LJava8$Logger;调用lambda表达式变成了invokedynamic指令
         5: invokestatic  #3        // Method myPrint:(LJava8$Logger;)V
         8: return
      LineNumberTable:
        line 8: 0
        line 9: 8

  private static void myPrint(Java8$Logger);
    descriptor: (LJava8$Logger;)V
    flags: ACC_PRIVATE, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #4                  // String hello lambda
         3: invokeinterface #5,  2            // InterfaceMethod Java8$Logger.log:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 12: 0
        line 13: 8
//新增的方法
  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 8: 0
}
SourceFile: "java8.java"
InnerClasses:
     static #11= #10 of #8; //Logger=class Java8$Logger of class Java8
     public static final #56= #55 of #58; //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/String;)V
      #29 invokestatic Java8.lambda$main$0:(Ljava/lang/String;)V
      #28 (Ljava/lang/String;)V

可以看到main方法中的lambda表达式变成了invokedynamic指令,并新增了一个新增的私有方法lambda$main$0lambda$main$0函数的逻辑就是lambda表达式中逻辑。

invokedynamic指令的第一个参数0表示的是在BootstrapMethods区中索引的值。这里BootstrapMethods索引0处格式化处后为:

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;

查看LambdaMetafactory[源码],(https://github.com/frohoff/jdk8u-jdk/blob/master/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java)该方法会在运行时动态在内存中生成一个实现函数式接口实例类型,并在其接口实现中调用在Java8中新增的lambda$main$0方法。我们可以使用以下命令验证:

java -Djdk.internal.lambda.dumpProxyClasses Java8

我们会在运行目录下看到多了一个Java8$$Lambda$1.class文件,用javap看一下:

javap -p -c Java8\$\$Lambda\$1.class

可以看到:

final class Java8$$Lambda$1 implements Java8$Logger {
  private Java8$$Lambda$1();
    Code:
       0: aload_0
       1: invokespecial #10                 // Method java/lang/Object."":()V
       4: return

  public void log(java.lang.String);
    Code:
       0: aload_1
       1: invokestatic  #18                 // Method Java8.lambda$main$0:(Ljava/lang/String;)V
       4: return
}

确实在运行时生成了一个Java8$$Lambda$1类,并且接口实现为调用生成的Java8.lambda$main$0方法。

到这里整个lambda实现原理就比较清晰了,大致流程为:

                

lambda -----> invokedynamic(.classs), lambda$main$0 ------> Java8$Logger.class(在内存) ---> Java8$Logger.log ---> lambda$main$0
                    ^                                                                                       ^
                    |                                                                                       |
                javac                                                   LambdaMetafactory.metafactory
                    

D8实现lambda脱糖的基本原理

原理

Android 官方文档 或 AOSP 的源码中是没有LambdaMetafactory类的,因此就无法通过java.lang.invoke.LambdaMetafactory类中的metafactory方法,运行时中即时创建 lambda相关的匿名类,Android d8中的实现基本原理为:

  • 为lambda表达式生成一个类,类名类似于:$$Lambda$xxx$yyy,其中xxx为lambda源码实现所在的类名,yyy为一串类似md5字符串的值。具体生成算法暂未深入研究。该类会继承于函数式接口,并且有一个public static的名为INSTANCE的字段,该字段的类型为$$Lambda$xxx$yyy,在类cinit中(经典的单例实现)。接口的抽象方法在实现很简单,就是调用一个名为xxx.lambda$zzz$N方法,xxx为lambda源码实现所在的类名,zzz为lambda源码实现所在的类的方法名,N为阿拉伯数字。xxx.lambda$zzz$N其实就是lambda表达式的具体逻辑的藏身之处。

  • xxx.lambda$zzz$N做为lambda实现的类,它是一个位于lambda实现类中的静态方法。如前文所述,它就是lambda表达式的具体实现。

  • 在lambda表达式调用的地方,会将lambda表达式替换为$$Lambda$xxx$yyy.INSTANCE。在后续调用接口方法,就直接调用$$Lambda$xxx$yyy.INSTANCE的接口实现,接着转调到实现类中的xxx.lambda$zzz$N方法,执行lambda真正的逻辑。

实例

源码使用测试Demo相同的代码。

编译并dex:

javac *.java
java -jar $ANDROID_HOME/build-tools/28.0.3/lib/d8.jar \
    --lib $ANDROID_HOME/platforms/android-28/android.jar \
    --release \
    --output . \
    *.class

解压出dex:

$ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex

输出:

Processing 'classes.dex'...
Opened 'classes.dex', DEX version '035'

// Logger接口
Class #0            -
  Class descriptor  : 'LJava8$Logger;'
  Access flags      : 0x0600 (INTERFACE ABSTRACT)
  Superclass        : 'Ljava/lang/Object;'
  ...
    #0              : (in LJava8$Logger;)
      name          : 'log'
      type          : '(Ljava/lang/String;)V'
...

Class #1            -
  Class descriptor  : 'LJava8;'
  Access flags      : 0x0000 ()
  Superclass        : 'Ljava/lang/Object;'
  Interfaces        -
  Static fields     -
  Instance fields   -
  Direct methods    -
    #0              : (in LJava8;)
      name          : ''
 ...

// 生成一个名为'lambda$main$0'的静态方法
    #1              : (in LJava8;)
      name          : 'lambda$main$0'
      type          : '(Ljava/lang/String;)V'
      access        : 0x1008 (STATIC SYNTHETIC)
      code          -
      registers     : 2
      ins           : 1
      outs          : 2
      insns size    : 6 16-bit code units
// lambda$main$0方法的实现
// System.out.println(s)
000270:                                        |[000270] Java8.lambda$main$0:(Ljava/lang/String;)V
000280: 6200 0100                              |0000: sget-object v0, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001
000284: 6e20 0800 1000                         |0002: invoke-virtual {v0, v1}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0008
....

//main方法
    #2              : (in LJava8;)
      name          : 'main'
      type          : '([Ljava/lang/String;)V'
      access        : 0x0089 (PUBLIC STATIC VARARGS)
    ....
00028c:                                        |[00028c] Java8.main:([Ljava/lang/String;)V
//获取$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY类中名为INSTANCE的字段(它是$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY类的单例),存到变量v0中
00029c: 6200 0000                              |0000: sget-object v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.INSTANCE:L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // field@0000
//调用myPrint静态方法,并将前一步获取到的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY.INSTANCE做为参数传给myPrint。
0002a0: 7110 0700 0000                         |0002: invoke-static {v0}, LJava8;.myPrint:(LJava8$Logger;)V // method@0007
...      

//myPrint方法
    #3              : (in LJava8;)
      name          : 'myPrint'
      type          : '(LJava8$Logger;)V'
      access        : 0x000a (PRIVATE STATIC)
...
0002a8:                                        |[0002a8] Java8.myPrint:(LJava8$Logger;)V
// "hello lambda"存放到v0
0002b8: 1a00 1200                              |0000: const-string v0, "hello lambda" // string@0012
// 这里v1即为传进来的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY.INSTANCE,v0为"hello lambda"参数,调用log方法。
0002bc: 7220 0300 0100                         |0002: invoke-interface {v1, v0}, LJava8$Logger;.log:(Ljava/lang/String;)V // method@0003
0002c2: 0e00                                   |0005: return-void
 ...

//lambda表达式生成的类: $$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
Class #2            -
  Class descriptor  : 'L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;'
  Access flags      : 0x1011 (PUBLIC FINAL SYNTHETIC)
  ...
  
// 定一个了INSTANCE静态字段,类型为$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
  Static fields     -
    #0              : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
      name          : 'INSTANCE'
      type          : 'L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;'
      access        : 0x1019 (PUBLIC STATIC FINAL SYNTHETIC)
  Instance fields   -
  
 // 类初始化方法,初始化INSTANCE字段
  Direct methods    -
    #0              : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
      name          : ''
      type          : '()V'
      access        : 0x11008 (STATIC SYNTHETIC CONSTRUCTOR)
...
000208:                                        |[000208] -..Lambda.Java8.QkyWJ8jlAksLjYziID4cZLvHwoY.:()V
// new 一个$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY
000218: 2200 0000                              |0000: new-instance v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // type@0000
//调用 $$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY初始化函数init
00021c: 7010 0100 0000                         |0002: invoke-direct {v0}, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.:()V // method@0001

// 将初始化好的$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY对象存放到INSTANCE字段
000222: 6900 0000                              |0005: sput-object v0, L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;.INSTANCE:L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY; // field@0000
000226: 0e00                                   |0007: return-void
....


// 实现log方法
  Virtual methods   -
    #0              : (in L-$$Lambda$Java8$QkyWJ8jlAksLjYziID4cZLvHwoY;)
      name          : 'log'
      type          : '(Ljava/lang/String;)V'
      access        : 0x0011 (PUBLIC FINAL)
... 
000240:                                        |[000240] -..Lambda.Java8.QkyWJ8jlAksLjYziID4cZLvHwoY.log:(Ljava/lang/String;)V
000250: 7110 0500 0100                         |0000: invoke-static {v1}, LJava8;.lambda$main$0:(Ljava/lang/String;)V // method@0005
....

具体实现已在注释中说明,可以看到d8的实现思路和JDK中的实现思路是一致的,d8只是将JDK在运行时java.lang.invoke.LambdaMetafactory::metafactory的实现搬到了编译期执行。

参考文献

  • Android's Java 8 Support

  • Android 兼容 Java 8 语法特性的原理分析

  • 理解invokedynamic

你可能感兴趣的:(java &d8中Lambda的实现原理)