• Java8 Lambda表达式原理扫盲


    背景

    在使用Lamdba表达式,一直以为是内部类的方式实现的,但是一想如果每次调用都实例化一个内部类,性能肯定不好,难道Java里的lambda表达式真的是这么实现的吗?也许是该研究下原理了。

    正文

    1. 测试代码:
    public class Test{
    	public void test() {
    	    Runnable r = () -> System.out.println(123);
    	    r.run();
    	}
    }
    

    执行编译命令javac -g Test.java,得到class文件。

    1. 查看字节码

    查看字节码javap -p -verbose Test得到:

    Classfile /Users/liushijie/learn/Test.class
      Last modified Nov 20, 2018; size 1058 bytes
      MD5 checksum febbe61fdc1f4564d2e039067752d6fc
      Compiled from "Test.java"
    public class Test
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #7.#21         // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#26         // #0:run:()Ljava/lang/Runnable;
       #3 = InterfaceMethodref #27.#28        // java/lang/Runnable.run:()V
       #4 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = Methodref          #31.#32        // java/io/PrintStream.println:(I)V
       #6 = Class              #33            // Test
       #7 = Class              #34            // java/lang/Object
       #8 = Utf8               <init>
       #9 = Utf8               ()V
      #10 = Utf8               Code
      #11 = Utf8               LineNumberTable
      #12 = Utf8               LocalVariableTable
      #13 = Utf8               this
      #14 = Utf8               LTest;
      #15 = Utf8               test
      #16 = Utf8               r
      #17 = Utf8               Ljava/lang/Runnable;
      #18 = Utf8               lambda$test$0
      #19 = Utf8               SourceFile
      #20 = Utf8               Test.java
      #21 = NameAndType        #8:#9          // "<init>":()V
      #22 = Utf8               BootstrapMethods
      #23 = MethodHandle       #6:#35         // 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;
      #24 = MethodType         #9             //  ()V
      #25 = MethodHandle       #6:#36         // invokestatic Test.lambda$test$0:()V
      #26 = NameAndType        #37:#38        // run:()Ljava/lang/Runnable;
      #27 = Class              #39            // java/lang/Runnable
      #28 = NameAndType        #37:#9         // run:()V
      #29 = Class              #40            // java/lang/System
      #30 = NameAndType        #41:#42        // out:Ljava/io/PrintStream;
      #31 = Class              #43            // java/io/PrintStream
      #32 = NameAndType        #44:#45        // println:(I)V
      #33 = Utf8               Test
      #34 = Utf8               java/lang/Object
      #35 = Methodref          #46.#47        // 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;
      #36 = Methodref          #6.#48         // Test.lambda$test$0:()V
      #37 = Utf8               run
      #38 = Utf8               ()Ljava/lang/Runnable;
      #39 = Utf8               java/lang/Runnable
      #40 = Utf8               java/lang/System
      #41 = Utf8               out
      #42 = Utf8               Ljava/io/PrintStream;
      #43 = Utf8               java/io/PrintStream
      #44 = Utf8               println
      #45 = Utf8               (I)V
      #46 = Class              #49            // java/lang/invoke/LambdaMetafactory
      #47 = NameAndType        #50:#54        // 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;
      #48 = NameAndType        #18:#9         // lambda$test$0:()V
      #49 = Utf8               java/lang/invoke/LambdaMetafactory
      #50 = Utf8               metafactory
      #51 = Class              #56            // java/lang/invoke/MethodHandles$Lookup
      #52 = Utf8               Lookup
      #53 = Utf8               InnerClasses
      #54 = 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;
      #55 = Class              #57            // java/lang/invoke/MethodHandles
      #56 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #57 = Utf8               java/lang/invoke/MethodHandles
    {
      public Test();
        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 1: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   LTest;
    
      public void test();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=2, args_size=1
             0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
             5: astore_1
             6: aload_1
             7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
            12: return
          LineNumberTable:
            line 3: 0
            line 4: 6
            line 5: 12
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      13     0  this   LTest;
                6       7     1     r   Ljava/lang/Runnable;
    
      private static void lambda$test$0();
        descriptor: ()V
        flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
        Code:
          stack=2, locals=0, args_size=0
             0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: bipush        123
             5: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
             8: return
          LineNumberTable:
            line 3: 0
    }
    SourceFile: "Test.java"
    InnerClasses:
         public static final #52= #51 of #55; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
    BootstrapMethods:
      0: #23 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:
          #24 ()V
          #25 invokestatic Test.lambda$test$0:()V
          #24 ()V
    
    

    通过字节码文件我们可以看到,编译出来的字节码文件中新增一些玩意:

    1. 常量池里(#2)多了一个以前没有见过的InvokeDynamic指令
    2. 新增了一个静态私有方法:private static void lambda$test$0(),里面的内容正好是lambda表达式里的代码;
    3. 新增了一个BootstrapMethods属性,内部包含一个动态调用点列表,因为测试代码只有一个lambda表达式,所以我们只能看到一个调用点

    在运行时有一个链接(link)过程,在JVM层面调用。通过链接操作,调用上面3中的调用点,调用点在动态生成实现了FunctionInterface接口的类,方法中则调用2中新增的lambda$test$0方法,生成类之后通过构造函数实例化一个对象,被调用点持有,调用点有一个字的常量池。在调用invokedynamic指令之前会发生链接过程。下文引自:参考5
    里面也有提到过多线程场景,略过不提。

    Before the JVM can execute a dynamic call site (an invokedynamic instruction), the call site must first be linked. Linking is accomplished by calling a bootstrap method which is given the static information content of the call site, and which must produce a method handle that gives the behavior of the call site.
    

    总结

    通过一些验证和资料检索,大概了解lambda的原理,是使用指令与动态生成的内部类来完成调用,而且正常只会被链接一次。从这个点上来看,对性能是没有什么损失的,可以放心的使用。

    问题

    自己梳理的比较肤浅,没有深挖最底层的实现。LambdaMetafactory.metafactory动态调用点的链接过程比较长,如果有动态调用的场景应该是可以参考的。翻到过一篇问答(见参考6),暂时没找到合适的场景使用,没有深入下去的动力。

    参考

    1. Lambda表达式实现方式
    2. InvokeDynamic指令JSR 292
    3. https://stackoverflow.com/questions/26775676/explicit-use-of-lambdametafactory
    4. https://zhuanlan.zhihu.com/p/27159693
    5. https://docs.oracle.com/javase/8/docs/api/java/lang/invoke/package-summary.html#package.description
    6. https://stackoverflow.com/questions/26775676/explicit-use-of-lambdametafactory
  • 相关阅读:
    阶乘
    如何利用”七牛云”在UEditor实现图片的上传和浏览
    ueditor保存出现 从客户端(Note="<p>12345</p>")中检测到有潜在危险的 Request.Form 值
    配置进程外的Session
    数据库增加索引
    判断HTML中的checkbox是否被选中
    Oracle 配置文件目录
    Oracle 游标
    Oracle 分区表的索引、分区索引
    Oracle 索引
  • 原文地址:https://www.cnblogs.com/liushijie/p/9991255.html
Copyright © 2020-2023  润新知