程序员开发经验随笔

lambda表达式运行机制

2019-06-12  本文已影响31人  山东大葱哥

lambda表达式运行机制

在看字节码细节之前,先来了解一下lambda表达式如何脱糖(desugar)。lambda的语法糖在编译后的字节流Class文件中,会通过invokedynamic指令指向一个bootstrap方法(下文中部分会称作“引导方法”),这个方法就是java.lang.invoke.LambdaMetafactory中的一个静态方法。通过debug的方式,就可以看到该方法的执行,此方法源码如下:

    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();
    }

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

CallSite buildCallSite() throws LambdaConversionException {
        // 通过字节码生成技术(jdk asm)生成代表lambda表达式体信息的一个内部类的Class对象,因为是运行期生成,所以在编译后的字节码信息中并没有这个内部类的字节流信息。
        final Class<?> innerClass = spinInnerClass();
        // incokedType即lambda表达式的调用方法类型,如下面示例中的Consumer方法
        if (invokedType.parameterCount() == 0) {
            final Constructor<?>[] ctrs = AccessController.doPrivileged(
                    new PrivilegedAction<Constructor<?>[]>() {
                @Override
                public Constructor<?>[] run() {
                    Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
                    if (ctrs.length == 1) {
                        // 表示lambda表达式的内部类是私有的,所以需要获取这个内部类的访问权限。
                        ctrs[0].setAccessible(true);
                    }
                    return ctrs;
                }
                    });
            if (ctrs.length != 1) {
                throw new LambdaConversionException("Expected one lambda constructor for "
                        + innerClass.getCanonicalName() + ", got " + ctrs.length);
            }

            try {
                // 通过构造函数的newInstance方法,创建一个内部类对象
                Object inst = ctrs[0].newInstance();
                // MethodHandles.constant方法将这个内部类对象的信息组装并绑定到一个MethodHandle对象,作为ConstantCallSite的构造函数的参数“target”返回。后面对于Lambda表达式的调用,都会通过MethodHandle直接调用,不需再次生成CallSite.
                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);
            }
        }
    }

这个过程将生成一个代表lambda表达式信息的内部类(也就是方法第一行的innerClass,这个类是一个 functional 类型接口的实现类),这个内部类的Class字节流是通过jdk asm 的ClassWriter,MethodVisitor,生成,然后再通过调用Constructor.newInstance方法生成这个内部类的对象,并将这个内部类对象绑定给一个MethodHandle对象,然后这个MethodHandle对象传给CallSite对象(通过CallSite的构造函数赋值)。所以这样就完成了一个将lambda表达式转化成一个内部类对象,然后将内部类通过MethodHandle绑定到一个CallSite对象。CallSite对象就相当于lambda表达式的一个勾子。而invokedynamic指令就链接到这个CallSite对象来实现运行时绑定,也即invokedynamic指令在调用时,会通过这个勾子找到lambda所代表的一个functional接口对象(也即MethodHandle对象)。所以lambda的脱糖也就是在运行期通过bootstrap method的字节码信息,转化成一个MethodHandle的过程。
通过打印consumer对象的className(greeter.getClass().getName())可以得到结果是eight.Functionnal$$Lambda$1/659748578前面字符是Lambda表达式的ClassName,后面的659748578是刚才所述内部类的hashcode值。

上一篇下一篇

猜你喜欢

热点阅读