澳门新蒲京娱乐

新蒲京官方下载 2
【新蒲京官方下载】使用指南

方法深入理解新蒲京官方下载:

通过字节码分析JDK8中Lambda表达式编译及执行机制

LambdaMetafactory.metafactory

现在我们可以重点关注以下 LambdaMetafactory.metafactory的实现。

public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {返回值类型
        AbstractValidatingLambdaMetafactory mf;
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();
    }

实际是由InnerClassLambdaMetafactorybuildCallSite来生成。
生成之前会调用validateMetafactoryArgs方法校验目标类型(SAM)方法的参数/和产生的方法的参数/返回值类型是否一致。

metaFactory方法的参数:

  • caller: 由JVM提供的lookup context
  • invokedName: JVM提供的NameAndType
  • invokedType: JVM提供的期望的CallSite类型
  • samMethodType: 函数式接口定义的方法的签名
  • implMethod: 编译时产生的那个实现方法
  • instantiatedMethodType: 强制的方法签名和返回类型,
    一般和samMethodType相同或者是它的一个特例

上面的代码基本上是InnerClassLambdaMetafactory.buildCallSite的包装,下面看看这个方法的实现:

CallSite buildCallSite() throws LambdaConversionException {
       final Class<?> innerClass = spinInnerClass();
       if (invokedType.parameterCount() == 0) {
        ..... //调用构造函数初始化一个SAM的实例
           return new ConstantCallSite(MethodHandles.constant(samBase, inst));
       } else {
           UNSAFE.ensureClassInitialized(innerClass);
               return new ConstantCallSite(
                       MethodHandles.Lookup.IMPL_LOOKUP
                            .findStatic(innerClass, NAME_FACTORY, invokedType));
       }
   }

其中spinInnerClass调用asm框架动态的产生SAM的实现类,
这个实现类的的方法将会调用编译时产生的那个实现方法。
你可以在编译的时候加上参数-Djdk.internal.lambda.dumpProxyClasses,
这样编译的时候会自动产生运行时spinInnerClass产生的类。

你可以访问OpenJDK的bug系统了解这个功能。 JDK-8023524

  1. 第二条指令:5: astore_1
    指令起始偏移位置是5,主要取决于前面一个指令(invokedynamic)有两个操作数,每个操作数占两个字节(u2)空间,所以第二条指令就是从字节偏移位置5开始(后续的偏移地址将不再解释)。此指令执行后,当前方法的栈帧结构如下(注:此图没有画出当前栈帧的动态链接以及返回地址的数据结构,图中:左侧局部变量表,右侧操作数栈):

Lambda表达式的转换策略

Brian
Goetz是Oracle的Java语言架构师,
JSR 335(Lambda Expression)规范的lead, 写了几篇Lambda设计方面的文章,
其中之一就是Translation of Lambda
Expressions。
这篇文章介绍了Java 8 Lambda设计时的考虑以及实现方法。

他提到, Lambda表达式可以通过内部类, method handle, dynamic
proxy等方式实现, 但是这些方法各有优劣。 真正要实现Lambda表达式,
必须兼顾两个目标:
一是不引入特定策略,以期为将来的优化提供最大的灵活性,
二是保持类文件格式的稳定。 通过Java 7中引入的invokedynamic (JSR
292), 可以很好的兼顾这两个目标。

invokedynamic 在缺乏静态类型信息的情况下可以支持有效的灵活的方法调用。主要是为了日益增长的运行在JVM上的动态类型语言,
如Groovy, JRuby。

invokedynamic将Lambda表达式的转换策略推迟到运行时,
这也意味着我们现在编译的代码在将来的转换策略改变的情况下也能正常运行。

编译器在编译的时候, 会将Lambda表达式的表达式体 (lambda
body)脱糖(desugar)
成一个方法,此方法的参数列表和返回类型和lambda表达式一致,
如果有捕获参数, 脱糖的方法的参数可能会更多一些,
并会产生一个invokedynamic调用, 调用一个call site。 这个call
site被调用时会返回lambda表达式的目标类型(functional
interface)的一个实现类。 这个call site称为这个lambda表达式的lambda
factory。 lambda factory的Bootstrap方法是一个标准方法,
叫做lambda metafactory。

编译器在转换lambda表达式时,
可以推断出表达式的参数类型,返回类型以及异常,
称之为natural signature
我们将目标类型的方法签名称之为lambda descriptor, lambda
factory的返回对象实现了函数式接口, 并且关联的表达式的代码逻辑,
称之为lambda object

新蒲京官方下载 1

重复的lambda表达式

下面的代码中,在一个循环中重复生成调用lambda表达式,只会生成同一个lambda对象,
因为只有同一个invokedynamic指令。

for (int i = 0; i<100; i++){
    Consumer<String> c = s -> System.out.println(s);
    System.out.println(c.hashCode());
}

但是下面的代码会生成两个lambda对象,
因为它会生成两个invokedynamic指令。

Consumer<String> c = s -> System.out.println(s);
System.out.println(c.hashCode());
Consumer<String> c2 = s -> System.out.println(s);
System.out.println(c2.hashCode());
  1. 第九条:21: return方法返回,因为是void方法,所以就是opcode就是return。此时操作数栈和局部变量表都是空,方法返回。最后再画上一笔:

再了解了Java 8 Lambda的一些基本概念和应用后,
我们会有这样的一个问题: Lambda表达式被编译成了什么?。
这是一个有趣的问题,涉及到JDK的具体的实现。
本文将介绍OpenJDK对Lambda表达式的转换细节, 读者可以了解Java 8
Lambda表达式背景知识。

  1. 第三条: 6: aload_1将greeter 弹出局部变量表,压入操作数栈。

方法引用

public static void main(String[] args) throws Throwable {

    Consumer<String> c  = System.out::println;
    c.accept("hello");
}

这段代码不会产生一个类似”Lambda$0″新方法。
因为LambdaMetafactory会直接使用这个引用的方法。

BootstrapMethods:
  0: #51 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:
      #52 (Ljava/lang/Object;)V
      #59 invokevirtual java/io/PrintStream.println:(Ljava/lang/String;)V
      #60 (Ljava/lang/String;)V

#59指示实现方法为System.out::println

lambda表达式对应一个incokedynamic指令,通过指令在常量池的符号引用,可以得到BootstrapMethods属性表对应的引导方法。在运行时,JVM会通过调用这个引导方法生成一个含有MethodHandle(CallSite的target属性)对象的CallSite作为一个Lambda的回调点。Lambda的表达式信息在JVM中通过字节码生成技术转换成一个内部类,这个内部类被绑定到MethodHandle对象中。每次执行lambda的时候,都会找到表达式对应的回调点CallSite执行。一个CallSite可以被多次执行(在多次调用的时候)。如下面这种情况,只会有一个invokedynamic指令,在comparator调用comparator.compare或comparator.reversed方法时,都会通过CallSite找到其内部的MethodHandle,并通过MethodHandle调用Lambda的内部表示形式LambdaForm。

捕获的变量等价于’final’

我们知道,在匿名类中调用外部的参数时,参数必须声明为final

Lambda体内也可以引用上下文中的变量,变量可以不声明成final的,但是必须等价于final

下面的例子中变量capturedV等价与final, 并没有在上下文中重新赋值。

public class Lambda5 {
    String greeting = "hello";

    public static void main(String[] args) throws Throwable {

        Lambda5 capturedV = new Lambda5();
        Consumer<String> c = s -> System.out.println(capturedV.greeting + " " + s);
        c.accept("captured variable");
        //capturedV = null; //Local variable capturedV defined in an enclosing scope must be final or effectively final
        //capturedV.greeting = "hi";
    }
}

如果反注释capturedV = null;编译出错,因为capturedV在上下文中被改变。

但是如果反注释capturedV.greeting = "hi"; 则没问题,
因为capturedV没有被重新赋值, 只是它指向的对象的属性有所变化。

新蒲京官方下载 2

转换举例

以上的解释有点晦涩, 简单来说

  • 编译时
    • Lambda 表达式会生成一个方法, 方法实现了表达式的代码逻辑
    • 生成invokedynamic指令, 调用bootstrap方法,
      由java.lang.invoke.LambdaMetafactory.metafactory方法实现
  • 运行时
    • invokedynamic指令调用metafactory方法。 它会返回一个CallSite,
      此CallSite返回目标类型的一个匿名实现类,
      此类关联编译时产生的方法
    • lambda表达式调用时会调用匿名实现类关联的方法。

最简单的一个lambda表达式的例子:

public class Lambda1 {
    public static void main(String[] args) {
        Consumer<String> c = s -> System.out.println(s);
        c.accept("hello lambda");
    }
}

使用javap查看生成的字节码 javap -c -p -v com/colobu/lambda/chapter5/Lambda1.class:

[root@colobu bin]# javap -c -p -v com/colobu/lambda/chapter5/Lambda1.class 
Classfile /mnt/eclipse/Lambda/bin/com/colobu/lambda/chapter5/Lambda1.class
  Last modified Nov 6, 2014; size 1401 bytes
  MD5 checksum fe2b2d3f039a9ba4209c488a8c4b4ea8
  Compiled from "Lambda1.java"
public class com.colobu.lambda.chapter5.Lambda1
  SourceFile: "Lambda1.java"
  BootstrapMethods:
    0: #57 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:
        #58 (Ljava/lang/Object;)V
        #61 invokestatic com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
        #62 (Ljava/lang/String;)V
  InnerClasses:
       public static final #68= #64 of #66; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             //  com/colobu/lambda/chapter5/Lambda1
   #2 = Utf8               com/colobu/lambda/chapter5/Lambda1
   #3 = Class              #4             //  java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          //  java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          //  "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/colobu/lambda/chapter5/Lambda1;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = NameAndType        #17:#18        //  accept:()Ljava/util/function/Consumer;
  #17 = Utf8               accept
  #18 = Utf8               ()Ljava/util/function/Consumer;
  #19 = InvokeDynamic      #0:#16         //  #0:accept:()Ljava/util/function/Consumer;
  #20 = String             #21            //  hello lambda
  #21 = Utf8               hello lambda
  #22 = InterfaceMethodref #23.#25        //  java/util/function/Consumer.accept:(Ljava/lang/Object;)V
  #23 = Class              #24            //  java/util/function/Consumer
  #24 = Utf8               java/util/function/Consumer
  #25 = NameAndType        #17:#26        //  accept:(Ljava/lang/Object;)V
  #26 = Utf8               (Ljava/lang/Object;)V
  #27 = Utf8               args
  #28 = Utf8               [Ljava/lang/String;
  #29 = Utf8               c
  #30 = Utf8               Ljava/util/function/Consumer;
  #31 = Utf8               LocalVariableTypeTable
  #32 = Utf8               Ljava/util/function/Consumer<Ljava/lang/String;>;
  #33 = Utf8               lambda$0
  #34 = Utf8               (Ljava/lang/String;)V
  #35 = Fieldref           #36.#38        //  java/lang/System.out:Ljava/io/PrintStream;
  #36 = Class              #37            //  java/lang/System
  #37 = Utf8               java/lang/System
  #38 = NameAndType        #39:#40        //  out:Ljava/io/PrintStream;
  #39 = Utf8               out
  #40 = Utf8               Ljava/io/PrintStream;
  #41 = Methodref          #42.#44        //  java/io/PrintStream.println:(Ljava/lang/String;)V
  #42 = Class              #43            //  java/io/PrintStream
  #43 = Utf8               java/io/PrintStream
  #44 = NameAndType        #45:#34        //  println:(Ljava/lang/String;)V
  #45 = Utf8               println
  #46 = Utf8               s
  #47 = Utf8               Ljava/lang/String;
  #48 = Utf8               SourceFile
  #49 = Utf8               Lambda1.java
  #50 = Utf8               BootstrapMethods
  #51 = Methodref          #52.#54        //  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;
  #52 = Class              #53            //  java/lang/invoke/LambdaMetafactory
  #53 = Utf8               java/lang/invoke/LambdaMetafactory
  #54 = NameAndType        #55:#56        //  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;
  #55 = Utf8               metafactory
  #56 = 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;
  #57 = MethodHandle       #6:#51         //  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;
  #58 = MethodType         #26            //  (Ljava/lang/Object;)V
  #59 = Methodref          #1.#60         //  com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
  #60 = NameAndType        #33:#34        //  lambda$0:(Ljava/lang/String;)V
  #61 = MethodHandle       #6:#59         //  invokestatic com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
  #62 = MethodType         #34            //  (Ljava/lang/String;)V
  #63 = Utf8               InnerClasses
  #64 = Class              #65            //  java/lang/invoke/MethodHandles$Lookup
  #65 = Utf8               java/lang/invoke/MethodHandles$Lookup
  #66 = Class              #67            //  java/lang/invoke/MethodHandles
  #67 = Utf8               java/lang/invoke/MethodHandles
  #68 = Utf8               Lookup
{
  public com.colobu.lambda.chapter5.Lambda1();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0       
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return        
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lcom/colobu/lambda/chapter5/Lambda1;
  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #19,  0             // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
         5: astore_1      
         6: aload_1       
         7: ldc           #20                 // String hello lambda
         9: invokeinterface #22,  2           // InterfaceMethod java/util/function/Consumer.accept:(Ljava/lang/Object;)V
        14: return        
      LineNumberTable:
        line 10: 0
        line 11: 6
        line 12: 14
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0      15     0  args   [Ljava/lang/String;
               6       9     1     c   Ljava/util/function/Consumer;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            6       9     1     c   Ljava/util/function/Consumer<Ljava/lang/String;>;
  private static void lambda$0(java.lang.String);
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #35                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: aload_0       
         4: invokevirtual #41                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         7: return        
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       8     0     s   Ljava/lang/String;
}

可以看到, Lambda表达式体被生成一个称之为lambda$0的方法。
看字节码知道它调用System.out.println输出传入的参数。

原lambda表达式处产生了一条invokedynamic #19, 0。它会调用bootstrap方法。

0: #57 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:
        #58 (Ljava/lang/Object;)V
        #61 invokestatic com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
        #62 (Ljava/lang/String;)V

如果Lambda表达式写成Consumer<String> c = (Consumer<String> & Serializable)s -> System.out.println(s);,
则BootstrapMethods的字节码为

BootstrapMethods:
    0: #108 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
      Method arguments:
        #109 (Ljava/lang/Object;)V
        #112 invokestatic com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
        #113 (Ljava/lang/String;)V
        #114 1

它调用的是LambdaMetafactory.altMetafactory,和上面的调用的方法不同。#114 1意味着要实现Serializable接口。

如果Lambda表达式写成“,则BootstrapMethods的字节码为

BootstrapMethods:
  0: #57 invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
    Method arguments:
      #58 (Ljava/lang/Object;)V
      #61 invokestatic com/colobu/lambda/chapter5/Lambda1.lambda$0:(Ljava/lang/String;)V
      #62 (Ljava/lang/String;)V
      #63 2
      #64 1
      #65 com/colobu/lambda/chapter5/ABC

#63 2意味着要实现额外的接口。#64 1意味着要实现额外的接口的数量为1。

字节码的指令含义可以参考这篇文章:Java bytecode instruction
listings。

可以看到,
Lambda表达式具体的转换是通过java.lang.invoke.LambdaMetafactory.metafactory实现的,
静态参数依照lambda表达式和目标类型不同而不同。

7.第八条:16: invokeinterface #6,
2调用了Consumer的accept接口方法{greeter.accept(person)}。#6逗号后面的参数2是invokeinterface指令的参数,含义是接口方法的参数的个数加1,因为accpet方法只有一个参数,所以这里是1+1=2。接着再看一下常量池项
#6属性表信息:

直接调用生成的方法

上面提到,
Lambda表达式体会由编译器生成一个方法,名字格式如Lambda$XXX

既然是类中的实实在在的方法,我们就可以直接调用。当然,
你在代码中直接写lambda$0()编译通不过,
因为Lambda表达式体还没有被抽取成方法。

但是在运行中我们可以通过反射的方式调用。
下面的例子使用发射和MethodHandle两种方式调用这个方法。

public static void main(String[] args) throws Throwable {
    Consumer<String> c = s -> System.out.println(s);
    Method m = Lambda4.class.getDeclaredMethod("lambda$0", String.class);
    m.invoke(null, "hello reflect");
    MethodHandle mh = MethodHandles.lookup().findStatic(Lambda4.class, "lambda$0", MethodType.methodType(void.class, String.class));
    mh.invoke("hello MethodHandle");
}
  • bootstrap_method_attr_index:指向bootstrap_methods的一个有效索引值,其结构在属性表的
    bootstrap method
    结构中,也描述在Class文件的二进制字节流信息里。下面是对应索引 0
    的bootstrap method 属性表的内容:

生成的类名

既然LambdaMetafactory会使用asm框架生成一个匿名类,
那么这个类的类名有什么规律的。

Consumer<String> c = s -> System.out.println(s);
System.out.println(c.getClass().getName());
System.out.println(c.getClass().getSimpleName());
System.out.println(c.getClass().getCanonicalName());

输出结果如下:

com.colobu.lambda.chapter5.Lambda3$$Lambda$1/640070680
Lambda3$$Lambda$1/640070680
com.colobu.lambda.chapter5.Lambda3$$Lambda$1/640070680

类名格式如 <包名>.<类名>$$Lambda$/.

number是由一个计数器生成counter.incrementAndGet()。

后缀/<NN>中的数字是一个hash值,
那就是类对象的hash值c.getClass().hashCode()

Klass::external_name()中生成。

sprintf(hash_buf, "/" UINTX_FORMAT, (uintx)hash);

在Class文件中,方法调用即是对常量池(ConstantPool)属性表中的一个符号引用,在类加载的解析期或者运行时才能确定直接引用。

简单解释下这个CONSTANT_InvokeDynamic_info的结构:

新蒲京官方下载 3

新蒲京官方下载 4

新蒲京官方下载 5

下面通过具体的字节码指令详细分析一下lambda的脱糖机制,并且看一下invokedynamic指令是怎么给lambda在JVM中的实现带来可能。如果前面所述过程还有不清晰,还可以参考下Oracle工程师在设计java8
Lambda表达式时候的一些思考:Translation of Lambda Expressions

关于方法调用的其他详细的解释可以参考官方文档《The Java® Virtual Machine
Specification Java8 Edition》-2.11.8 Method Invocation and Return
Instructions。

通过字节码分析JDK8中Lambda表达式编译及执行机制【面试+工作】

本文只是通过Consumer接口分析lambda表达式的字节码指令,以及运行时的脱糖过程。也是把操作码忘得差不多了,也顺便再回顾一下。


name_and_新蒲京官方下载 ,type_index:代表常量池表信息的一个有效索引值,其指向的常量池属性表结构一定是一个CONSTANT_NameAndType_info属性,代表了方法名称和方法描述符信息。再沿着
#44索引看一下常量池相关项的描述内容:

在运行时期,虚拟机会通过调用这个方法来返回一个CallSite(调用点)对象。简述一下方法的执行过程,首先,初始化一个InnerClassLambdaMetafactory对象,这个对象的buildCallSite方法会将Lambda表达式先转化成一个内部类,这个内部类是MethodHandles.Lookup
caller的一个内部类,也即包含此Lambda表达式的类的内部类。这个内部类是通过字节码生成技术(jdk.internal.org.objectweb.asm)生成,再通过UNSAFE类加载到JVM。然后再返回绑定此内部类的CallSite对象,这个过程的源码也可以看一下:

新蒲京官方下载 6

结合CONSTANT_InvokeDynamic_info的结构信息来看一下这个常量池表项包含的信息。

可以看到第一条指令就是代表了lambda表达式的实现指令,invokedynamic指令,这个指令是JSR-292开始应用的规范,而鉴于兼容和扩展的考虑(可以参考Oracle工程师对于使用invokedynamic指令的原因),JSR-337通过这个指令来实现了lambda表达式。也就是说,只要有一个lambda表达式,就会对应一个invokedynamic指令。

新蒲京官方下载 7

invokedynamic指令特性

  • tag:
    占用一个字节(u1)的tag,也即InvokeDynamic的一个标记值,其会转化成一个字节的tag值。可以看一下jvm
    spec中,常量池的tag值转化表(这里tag值对应=18):

结语

这个方法的前三个参数都是由JVM自动链接Call
Site生成。方法最后返回一个CallSite对象,对应invokedynamic指令的操作数。

新蒲京官方下载 8

先看一个简单的示例,示例使用了java.util.function包下面的Consumer。

通过以上几项,可以很清楚得到invokedynamic的方法描述信息。

这里为了画图方便,所以按照局部变量表和操作数栈的实际分配空间先画出了几个格子。因为字节码信息中已经告知了[stack=4, locals=2, args_size=1]。也就是局部变量表的实际运行时空间最大占用两个Slot(一个Slot一个字节,long,double类型变量需占用两个slot),操作数栈是4个slot,参数占一个slot。这里的args是main方法的String[]
args参数。因为是个static方法,所以也没有this变量的aload_0 指令。

通过打印consumer对象的className(greeter.getClass().getName())可以得到结果是eight.Functionnal$$Lambda$1/659748578前面字符是Lambda表达式的ClassName,后面的659748578是刚才所述内部类的hashcode值。

责任编辑:

以上可以看出Consumer接口的泛型被擦除(编译期间进行,所以字节码信息中并不会包含泛型信息),所以这里并不知道实际的参数操作数类型。但是这里可以得到实际对象的引用值,这里accept方法执行,greeter和person引用出栈,如下图:

用verbose命令看一下方法主体的字节码信息,这里暂时省略常量池信息,后面会在符号引用到常量池信息的地方具体展示。

  1. 第五条:10: dup复制操作数栈栈顶的值,并且将该值入操作数栈栈顶。dup指令是一种对于初始化过程的编译期优化。因前面的new操作码并不会真正的创建对象,而是push一个引用到操作数栈,所以dup之后,这个栈顶的复制引用就可以用来给调用初始化方法(构造函数)的invokespecial提供操作数时消耗掉,同时原有的引用值就可以给其他比如对象引用的操作码使用。此时栈帧结构如下图:

相关文章

No Comments, Be The First!
近期评论
    功能
    网站地图xml地图