美文网首页
Java lambda实现原理解析

Java lambda实现原理解析

作者: allanYan | 来源:发表于2022-10-25 10:12 被阅读0次

    概述

    Java8引入了lambda表达式,那么底层是如何实现的呢?是否是采用匿名内部类实现的呢?

    代码样例

    public class MyTest {
        public static void main(String[] args) {
            Runnable r = () -> System.out.println(Arrays.toString(args));
            r.run();
        }
    }
    

    字节码如下(javap -c -s -l -verbose -private lamb/MyTest.class):

    Classfile /Users/xmly/Works/example/target/classes/lamb/MyTest.class
      Last modified 2022-10-24; size 1270 bytes
      MD5 checksum 308b472813d9700f4dca0763aeeb505a
      Compiled from "MyTest.java"
    public class lamb.MyTest
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #8.#25         // java/lang/Object."<init>":()V
       #2 = InvokeDynamic      #0:#30         // #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
       #3 = InterfaceMethodref #31.#32        // java/lang/Runnable.run:()V
       #4 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = Methodref          #35.#36        // java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
       #6 = Methodref          #37.#38        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Class              #39            // lamb/MyTest
       #8 = Class              #40            // java/lang/Object
       #9 = Utf8               <init>
      #10 = Utf8               ()V
      #11 = Utf8               Code
      #12 = Utf8               LineNumberTable
      #13 = Utf8               LocalVariableTable
      #14 = Utf8               this
      #15 = Utf8               Llamb/MyTest;
      #16 = Utf8               main
      #17 = Utf8               ([Ljava/lang/String;)V
      #18 = Utf8               args
      #19 = Utf8               [Ljava/lang/String;
      #20 = Utf8               r
      #21 = Utf8               Ljava/lang/Runnable;
      #22 = Utf8               lambda$main$0
      #23 = Utf8               SourceFile
      #24 = Utf8               MyTest.java
      #25 = NameAndType        #9:#10         // "<init>":()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;
      #28 = MethodType         #10            //  ()V
      #29 = MethodHandle       #6:#42         // invokestatic lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
      #30 = NameAndType        #43:#44        // run:([Ljava/lang/String;)Ljava/lang/Runnable;
      #31 = Class              #45            // java/lang/Runnable
      #32 = NameAndType        #43:#10        // run:()V
      #33 = Class              #46            // java/lang/System
      #34 = NameAndType        #47:#48        // out:Ljava/io/PrintStream;
      #35 = Class              #49            // java/util/Arrays
      #36 = NameAndType        #50:#51        // toString:([Ljava/lang/Object;)Ljava/lang/String;
      #37 = Class              #52            // java/io/PrintStream
      #38 = NameAndType        #53:#54        // println:(Ljava/lang/String;)V
      #39 = Utf8               lamb/MyTest
      #40 = Utf8               java/lang/Object
      #41 = Methodref          #55.#56        // 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;
      #42 = Methodref          #7.#57         // lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
      #43 = Utf8               run
      #44 = Utf8               ([Ljava/lang/String;)Ljava/lang/Runnable;
      #45 = Utf8               java/lang/Runnable
      #46 = Utf8               java/lang/System
      #47 = Utf8               out
      #48 = Utf8               Ljava/io/PrintStream;
      #49 = Utf8               java/util/Arrays
      #50 = Utf8               toString
      #51 = Utf8               ([Ljava/lang/Object;)Ljava/lang/String;
      #52 = Utf8               java/io/PrintStream
      #53 = Utf8               println
      #54 = Utf8               (Ljava/lang/String;)V
      #55 = Class              #58            // java/lang/invoke/LambdaMetafactory
      #56 = NameAndType        #59:#63        // 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;
      #57 = NameAndType        #22:#17        // lambda$main$0:([Ljava/lang/String;)V
      #58 = Utf8               java/lang/invoke/LambdaMetafactory
      #59 = Utf8               metafactory
      #60 = Class              #65            // java/lang/invoke/MethodHandles$Lookup
      #61 = Utf8               Lookup
      #62 = Utf8               InnerClasses
      #63 = 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;
      #64 = Class              #66            // java/lang/invoke/MethodHandles
      #65 = Utf8               java/lang/invoke/MethodHandles$Lookup
      #66 = Utf8               java/lang/invoke/MethodHandles
    {
      public lamb.MyTest();
        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 5: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Llamb/MyTest;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=1
             0: aload_0
             1: invokedynamic #2,  0              // InvokeDynamic #0:run:([Ljava/lang/String;)Ljava/lang/Runnable;
             6: astore_1
             7: aload_1
             8: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
            13: return
          LineNumberTable:
            line 7: 0
            line 8: 7
            line 9: 13
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      14     0  args   [Ljava/lang/String;
                7       7     1     r   Ljava/lang/Runnable;
    
      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     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
             3: aload_0
             4: invokestatic  #5                  // Method java/util/Arrays.toString:([Ljava/lang/Object;)Ljava/lang/String;
             7: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            10: return
          LineNumberTable:
            line 7: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      11     0  args   [Ljava/lang/String;
    }
    SourceFile: "MyTest.java"
    InnerClasses:
         public static final #61= #60 of #64; //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 ()V
          #29 invokestatic lamb/MyTest.lambda$main$0:([Ljava/lang/String;)V
          #28 ()V
    
    

    分析

    可以看到编译后的字节码中出现了几个新的内容,lambda表达式编译后变成了

    invokedynamic #2,  0  //0 是预留参数,暂时没有作用
    #2 = InvokeDynamic      #0:#30 
    #30 = NameAndType        #43:#44  
    #43 = Utf8               run
    #44 = Utf8               ([Ljava/lang/String;)Ljava/lang/Runnable;
    

    其中0为表示在 [Bootstrap methods表]中的索引:
    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;

    javac编译

    可以看到编译后的字节码里面多了BSM相关的信息,说明javac编译对lambda做了特殊处理:

    javac命令的代码调用路径为:
    com.sun.tools.javac.Main->com.sun.tools.javac.main.Main->com.sun.tools.javac.main.JavaCompiler.compile2()->com.sun.tools.javac.main.JavaCompiler.desugar()

    if (this.source.allowLambda() && scanner.hasLambdas) {
        if (this.shouldStop(CompileState.UNLAMBDA)) {
            return;
        }
    
        env.tree = LambdaToMethod.instance(this.context).translateTopLevelClass(env, env.tree, localMake);
        this.compileStates.put(env, CompileState.UNLAMBDA);
    }
    
    截屏2022-10-25 09.16.11.png

    处理BSM相关的代码如下:

    private JCExpression makeMetafactoryIndyCall(LambdaToMethod.LambdaAnalyzerPreprocessor.TranslationContext<?> context, int refKind, Symbol refSym, List<JCExpression> indy_args) {
            JCFunctionalExpression tree = context.tree;
            MethodSymbol samSym = (MethodSymbol)this.types.findDescriptorSymbol(tree.type.tsym);
            List<Object> staticArgs = List.of(this.typeToMethodType(samSym.type), new MethodHandle(refKind, refSym, this.types), this.typeToMethodType(tree.getDescriptorType(this.types)));
            ListBuffer<Type> indy_args_types = new ListBuffer();
            Iterator var9 = indy_args.iterator();
    
            while(var9.hasNext()) {
                JCExpression arg = (JCExpression)var9.next();
                indy_args_types.append(arg.type);
            }
    
            MethodType indyType = new MethodType(indy_args_types.toList(), tree.type, List.nil(), this.syms.methodClass);
            Name metafactoryName = context.needsAltMetafactory() ? this.names.altMetafactory : this.names.metafactory;
            if (context.needsAltMetafactory()) {
                ListBuffer<Object> markers = new ListBuffer();
                Iterator var12 = tree.targets.tail.iterator();
    
                while(var12.hasNext()) {
                    Type t = (Type)var12.next();
                    if (t.tsym != this.syms.serializableType.tsym) {
                        markers.append(t.tsym);
                    }
                }
    
                int flags = context.isSerializable() ? 1 : 0;
                boolean hasMarkers = markers.nonEmpty();
                boolean hasBridges = context.bridges.nonEmpty();
                if (hasMarkers) {
                    flags |= 2;
                }
    
                if (hasBridges) {
                    flags |= 4;
                }
    
                staticArgs = staticArgs.append(flags);
                if (hasMarkers) {
                    staticArgs = staticArgs.append(markers.length());
                    staticArgs = staticArgs.appendList(markers.toList());
                }
    
                if (hasBridges) {
                    staticArgs = staticArgs.append(context.bridges.length() - 1);
                    Iterator var15 = context.bridges.iterator();
    
                    while(var15.hasNext()) {
                        Symbol s = (Symbol)var15.next();
                        Type s_erasure = s.erasure(this.types);
                        if (!this.types.isSameType(s_erasure, samSym.erasure(this.types))) {
                            staticArgs = staticArgs.append(s.erasure(this.types));
                        }
                    }
                }
    
                if (context.isSerializable()) {
                    int prevPos = this.make.pos;
    
                    try {
                        this.make.at(this.kInfo.clazz);
                        this.addDeserializationCase(refKind, refSym, tree.type, samSym, tree, staticArgs, indyType);
                    } finally {
                        this.make.at(prevPos);
                    }
                }
            }
    
            return this.makeIndyCall(tree, this.syms.lambdaMetafactory, metafactoryName, staticArgs, indyType, indy_args, samSym.name);
        }
    

    Name metafactoryName = context.needsAltMetafactory() ? this.names.altMetafactory : this.names.metafactory;可以知道lambda的BSM有两种:

    1. java.lang.invoke.LambdaMetafactory.metafactory
    2. java.lang.invoke.LambdaMetafactory.altMetafactory

    调用链观察

    从上面的分析,可以知道lambda最终会调用LambdaMetafactory.metafactory,通过打断点观察,其传入参数如下:

    截屏2022-10-25 09.45.50.png

    LambdaMetafactory.metafactory最终是调用InnerClassLambdaMetafactory.buildCallSite,代码如下:

           final Class<?> innerClass = spinInnerClass();
            if (invokedType.parameterCount() == 0) {
                final Constructor<?>[] ctrs = AccessController.doPrivileged(
                        new PrivilegedAction<Constructor<?>[]>() {
                    @Override
                    public Constructor<?>[] run() {
                        Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
                        if (ctrs.length == 1) {
                            // The lambda implementing inner class constructor is private, set
                            // it accessible (by us) before creating the constant sole instance
                            ctrs[0].setAccessible(true);
                        }
                        return ctrs;
                    }
                        });
                if (ctrs.length != 1) {
                    throw new LambdaConversionException("Expected one lambda constructor for "
                            + innerClass.getCanonicalName() + ", got " + ctrs.length);
                }
    
                try {
                    Object inst = ctrs[0].newInstance();
                    return new ConstantCallSite(MethodHandles.constant(samBase, inst));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception instantiating lambda object", e);
                }
            } else {
                try {
                    UNSAFE.ensureClassInitialized(innerClass);
                    return new ConstantCallSite(
                            MethodHandles.Lookup.IMPL_LOOKUP
                                 .findStatic(innerClass, NAME_FACTORY, invokedType));
                }
                catch (ReflectiveOperationException e) {
                    throw new LambdaConversionException("Exception finding constructor", e);
                }
            }
    

    spinInnerClass方法会使用unsafe.defineAnonymousClass来生成VM匿名内部类:

    package lamb;
    
    import java.lang.invoke.LambdaForm.Hidden;
    
    // $FF: synthetic class
    final class MyTest$$Lambda$1 implements Runnable {
        private final String[] arg$1;
    
        private MyTest$$Lambda$1(String[] var1) {
            this.arg$1 = var1;
        }
    
        private static Runnable get$Lambda(String[] var0) {
            return new MyTest$$Lambda$1(var0);
        }
    
        @Hidden
        public void run() {
            MyTest.lambda$main$0(this.arg$1);
        }
    }
    

    可以看到实际上是一个实现了Runnable接口的匿名类,最终会调用MyTest.lambdamain0方法;

    打印匿名类内容需要增加配置:-Djdk.internal.lambda.dumpProxyClasses=

    VM anonymous class与匿名内部类

    具体介绍可以参考这篇文章:https://www.zhihu.com/question/51132462/answer/124751186

    相关文章

      网友评论

          本文标题:Java lambda实现原理解析

          本文链接:https://www.haomeiwen.com/subject/oabgzrtx.html