背景

在使用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

Java8 Lambda表达式原理扫盲的更多相关文章

  1. java8 Lambda表达式的新手上车指南(1)

    背景 java9的一再推迟发布,似乎让我们恍然想起离发布java8已经过去了三年之久,java8应该算的上java语言在历代版本中变化最大的一个版本了,最大的新特性应该算得上是增加了lambda表达式 ...

  2. java8 Lambda表达式的新手上车指南(1)--基础语法和函数式接口

    背景 java9的一再推迟发布,似乎让我们恍然想起离发布java8已经过去了三年之久,java8应该算的上java语言在历代版本中变化最大的一个版本了,最大的新特性应该算得上是增加了lambda表达式 ...

  3. Java8 Lambda表达式详解手册及实例

    先贩卖一下焦虑,Java8发于2014年3月18日,距离现在已经快6年了,如果你对Java8的新特性还没有应用,甚至还一无所知,那你真得关注公众号"程序新视界",好好系列的学习一下 ...

  4. Java8 Lambda表达式(一)

    目录 一.应用场景引入 优化一:使用策略模式 优化二:使用匿名内部类 优化三:使用Lambda表达式 优化四:使用Stream API 二.Lambda运算符和对应语法 语法格式 Lambda表达式需 ...

  5. Java8 Lambda表达式、函数式接口和方法引用

    目录 Java8 Lambda表达式和函数式接口 Lambda表达式 Lambda的使用 函数式接口FunctionalInterface Java内置四大核心函数式接口 方法引用 构造器引用 Jav ...

  6. 一文搞懂Java8 Lambda表达式(附带视频教程)

    Lambda表达式介绍 Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁.通过Lambda表达式,可以替代我们以前经常写的匿名内部类来实现接口.Lambda表达式本质是一个 ...

  7. 【Java学习笔记之三十一】详解Java8 lambda表达式

    Java 8 发布日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Java 8之前 ...

  8. java8 快速入门 lambda表达式 Java8 lambda表达式10个示例

    本文由 ImportNew - lemeilleur 翻译自 javarevisited.欢迎加入翻译小组.转载请见文末要求. Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发 ...

  9. Java8 lambda表达式10个示例

    Java 8 刚于几周前发布,日期是2014年3月18日,这次开创性的发布在Java社区引发了不少讨论,并让大家感到激动.特性之一便是随同发布的lambda表达式,它将允许我们将行为传到函数里.在Ja ...

随机推荐

  1. Spring AOP 五大通知类型

    1.前置通知 在目标方法执行之前执行执行的通知. 前置通知方法,可以没有参数,也可以额外接收一个JoinPoint,Spring会自动将该对象传入,代表当前的连接点,通过该对象可以获取目标对象 和 目 ...

  2. 【spring源码分析】IOC容器初始化(十二)

    前言:在doCreateBean方法中还遗留一个问题没有分析:循环依赖.循环依赖在Spring中是非常重要的一个知识点,因此单独进行分析. 什么是循环依赖 循环依赖就是循环引用,两个或两个以上的bea ...

  3. 毕业设计(4):基于MicroPython的超声波倒车雷达系统

    前言 倒车雷达是汽车驻车或者倒车时的安全辅助装置,能以声音或者更为直观的显示告知驾驶员周围障碍物的情况,解除了驾驶员驻车.倒车和起动车辆时前后左右探视所引起的困扰,并帮助驾驶员扫除了视野死角和视线模糊 ...

  4. springmvc源码分析——入门看springmvc的加载过程

    本文将分析springmvc是如何在容器启动的时候将各个模块加载完成容器的创建的. 我知道在web.xml文件中我们是这样配置springmvc的: 可以看到,springmvc的核心控制器就是Dis ...

  5. Linux ISCSI服务配置

    Linux ISCSI服务配置,客户端进行访问 环境配置: server端 172.25.0.11 client端 172.25.0.10 配置ISCSI服务端 1.安装软件包 yum install ...

  6. EntityFramework 6.x和EntityFramework Core必须需要MultipleActiveResultSets?

    前言 本节我们来探讨到底需不需要在连接字符串上加上MultipleActiveResultSets = true ?,若您有更深层次的理解欢迎留下您的脚印. EntityFramework 6.x和E ...

  7. Graphic

    画圆操作 package demo1; import java.awt.Graphics; import javax.swing.*; import javax.swing.JPanel; publi ...

  8. Unity编辑器扩展

    在开发中有可能需要自己开发编辑器工具,在Unity中界面扩展常见两种情况,拿某插件为例: 1,自建窗口扩展 2,脚本Inspector显示扩展 不管使用那种样式,都需要经常用到两个类EditorGUI ...

  9. 【算法】C语言趣味程序设计编程百例精解

    C语言趣味程序设计编程百例精解 C/C++语言经典.实用.趣味程序设计编程百例精解(1)  https://wenku.baidu.com/view/b9f683c08bd63186bcebbc3c. ...

  10. 查看dll或lib中包含的函数

    这个功能主要用于检测是否包含自己所需的函数,可能在解决 error LNK2019 时会用到. dumpbin /exports avcodec.lib /out:test.txt