lambda表达式底层实现

news2024/11/5 8:59:28

一、lambda 代码 & 反编译

原始Java代码
假设我们有以下简单的Java程序,它使用Lambda表达式来遍历并打印一个字符串列表:

import java.util.Arrays;
import java.util.List;

public class LambdaExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("Apple", "Banana", "Cherry");
        items.forEach(item -> System.out.println(item));
    }
}

public interface Iterable<T> {
	default void forEach(Consumer<? super T> action) {
	        Objects.requireNonNull(action);
	        for (T t : this) {
	            action.accept(t);
	        }
	    }
}

CFR反编译结果:

/*
 * Decompiled with CFR 0.152.
 */
import java.lang.invoke.LambdaMetafactory;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

public class LambdaExample {
    public static void main(String[] stringArray) {
        List<String> list = Arrays.asList("Apple", "Banana", "Cherry");
        list.forEach(
        (Consumer<String>)LambdaMetafactory.metafactory(
        null, 
        null, 
        null, 
        (Ljava/lang/Object;)V, 
        lambda$main$0(java.lang.String ), 
        (Ljava/lang/String;)V)());
    }

    private static /* synthetic */ void lambda$main$0(String string) {
        System.out.println(string);
    }
}

这是程序的主方法,它创建了一个包含三个字符串的列表,并使用forEach方法遍历这个列表。在原始的Java代码中,这里很可能使用了一个Lambda表达式来打印列表中的每个元素。在反编译的代码中,Lambda表达式被转换成了对LambdaMetafactory.metafactory方法的调用,这个方法在运行时动态生成了一个实现了Consumer接口的类的实例。因为forEach方法入参就是一个函数式接口Consumer<? super T>,即:最终返回Consumer实例对象

二、反编译代码详解

2.1 LambdaMetafactory lambda元工厂类 方法:metafactory

/**
	 * 为了支持Java编程语言中的ambda表达式和方法引用表达式特性,
	 * 本方法提供了一种简便的方式来创建实现一个或多个接口的“函数对象”。这些函数对象是通过委托给一个提供的{@link MethodHandle},
	 * 在适当的类型适配和参数的部分求值之后实现的。通常作为{@code invokedynamic}调用点的<em>引导方法</em>使用。
	 *
	 * <p>这是标准的、简化的元工厂方法;通过{@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}
	 * 提供了额外的灵活性。关于此方法的行为的一般描述,请参见{@link LambdaMetafactory}。
	 *
	 * <p>当从此方法返回的{@code CallSite}的目标被调用时,生成的函数对象是实现由{@code invokedType}的返回类型命名的接口的类的实例,
	 * 声明了一个具有由{@code invokedName}和{@code samMethodType}给出的名称和签名的方法。它还可能覆盖来自{@code Object}的额外方法。
	 *
	 * @param caller 表示具有调用者访问权限的查找上下文。当与{@code invokedynamic}一起使用时,这由VM自动堆叠。
	 * @param invokedName 要实现的方法的名称。当与{@code invokedynamic}一起使用时,这由{@code InvokeDynamic}结构的{@code NameAndType}提供,并由VM自动堆叠。
	 * @param invokedType {@code CallSite}的预期签名。参数类型代表捕获变量的类型;返回类型是要实现的接口。当与{@code invokedynamic}一起使用时,这由{@code InvokeDynamic}结构的{@code NameAndType}提供,并由VM自动堆叠。如果实现方法是实例方法并且此签名有任何参数,则调用签名中的第一个参数必须对应于接收者。
	 * @param samMethodType 函数对象要实现的方法的签名和返回类型。
	 * @param implMethod 描述应在调用时调用的实现方法的直接方法句柄(适当地适配参数类型、返回类型,并将捕获的参数前置到调用参数中)。
	 * @param instantiatedMethodType 应在调用时动态强制执行的签名和返回类型。这可能与{@code samMethodType}相同,或可能是其特化版本。
	 * @return 一个CallSite,其目标可用于执行捕获,生成由{@code invokedType}命名的接口的实例
	 * @throws LambdaConversionException 如果违反了{@link LambdaMetafactory}中描述的任何链接不变量
	 */
    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
         // 创建一个内部类Lambda元工厂实例,用于生成和验证lambda表达式的实现
		 AbstractValidatingLambdaMetafactory mf;
		 mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        // 验证元工厂方法的参数是否符合要求
        mf.validateMetafactoryArgs();
        // 构建并返回一个CallSite,它是lambda表达式或方法引用的动态调用点
        return mf.buildCallSite();
    }

java.lang.invoke.LambdaMetafactory#metafactory 详解

metafactory是LambdaMetafactory中的一个静态方法,用于支持lambda表达式和方法引用表达式的动态实现。它是LambdaMetafactory类的一部分,该类是Java语言中lambda表达式和方法引用的底层支持机制。下面是对这段代码的详细解释:

  1. 方法的作用和目的
    这个方法的目的是为了动态创建一个实现特定接口的"函数对象"。这个函数对象通过委托给一个提供的MethodHandle(方法句柄),在适当的类型适配和参数的部分求值后,实现一个或多个接口。这通常用作invokedynamic调用点的引导方法(bootstrap method),以支持Java编程语言中的lambda表达式和方法引用表达式特性。

  2. 参数

    • 方法接收六个参数:callerinvokedNameinvokedTypesamMethodTypeimplMethodinstantiatedMethodType
      • caller:调用者,这个例子中就是LambdaExample类的MethodHandles.Lookup实例(每个类都可以通过调用MethodHandles.lookup()静态方法来获取一个与该类对应的MethodHandles.Lookup实例。这个Lookup实例代表了调用者的类,并且拥有创建方法句柄(MethodHandle)的权限,这些方法句柄可以访问调用者类中的成员,包括私有成员。),这个实例具有访问LambdaExample类中所有成员的权限。该参数是jvm自动填充。
      • invokedName:被调用方法的名称,在这个例子中,forEach方法接受一个java.util.function.Consumer类型的参数。Consumer接口定义了一个名为accept的抽象方法。因此,在这个上下文中,invokedName将是accept。 该参数是jvm自动填充。
      • invokedType:被调用方法的签名类型,这是一个java.lang.invoke.MethodType对象。在lambda表达式或方法引用的上下文中,invokedType描述了期望的调用点的签名,包括参数类型和返回类型。具体来说,invokedType参数定义了:
        ①调用点期望的参数类型,这些参数类型代表了lambda表达式或方法引用捕获的变量类型(如果有的话)。
        ②调用点期望的返回类型,这通常是一个函数式接口的类型,lambda表达式或方法引用将会生成一个实现了这个接口的对象。 在这个例子中,forEach方法接受一个java.util.function.Consumer类型的参数。Consumer接口定义了一个接受单个String参数且返回void的accept方法。因此,在这个上下文中,invokedType将是Consumer的方法签名,即接受一个String参数且返回void的方法类型。 该参数是jvm自动填充。
      • samMethodType:java.lang.invoke.LambdaMetafactory#metafactory方法的参数samMethodType指的是单抽象方法(Single Abstract Method, SAM)的方法类型。这是一个java.lang.invoke.MethodType对象,它描述了目标函数式接口中单个抽象方法的签名,包括参数类型和返回类型。
        在使用lambda表达式或方法引用时,通常会有一个函数式接口作为目标类型。函数式接口是指仅定义一个抽象方法的接口。samMethodType参数正是用来描述这个抽象方法的签名。在这个例子中,forEach方法接受一个java.util.function.Consumer类型的参数。Consumer是一个函数式接口,它定义了一个名为accept的抽象方法,该方法接受一个类型为T的参数并返回void。对于这个特定的例子,T是String类型,因此accept方法的签名是(String) -> void。
      • implMethod:java.lang.invoke.LambdaMetafactory#metafactory方法的参数implMethod指的是实现方法的MethodHandle。这个MethodHandle代表了lambda表达式或方法引用的实际实现体。在lambda表达式或方法引用被转换成动态方法调用时,implMethod就是那个被调用以执行具体操作的方法。
        具体来说,implMethod参数描述了:
        ①方法的实现:这是lambda表达式或方法引用中定义的逻辑的实际代码位置。
        ②方法的签名:通过MethodHandle的类型,它还隐含地指定了方法的参数类型和返回类型。
        在这个例子中,lambda表达式item -> System.out.println(item)对应的implMethod就是System.out.println(String)方法的MethodHandle。这个MethodHandle指向PrintStream类中的println(String)方法,这是因为System.out是一个PrintStream的实例。
      • instantiatedMethodType:指的是实例化方法的类型。这是一个java.lang.invoke.MethodType对象,它描述了在生成的lambda表达式或方法引用的实例中,目标方法的签名。具体来说,它定义了lambda表达式或方法引用在实现函数式接口时,该接口中抽象方法的调用签名,包括参数类型和返回类型。在这个例子中,forEach方法接受一个java.util.function.Consumer类型的参数。Consumer是一个函数式接口,它定义了一个名为accept的抽象方法,该方法接受一个类型为String的参数并返回void。因此,对于这个特定的例子,instantiatedMethodType将是描述accept方法签名的MethodType对象,即接受一个String参数且返回void的方法类型。
  3. 逻辑解释

    • AbstractValidatingLambdaMetafactory mf;:声明一个AbstractValidatingLambdaMetafactory类型的变量mf,这是一个抽象类,用于验证lambda工厂的参数。
    • mf = new InnerClassLambdaMetafactory(caller, invokedType, invokedName, samMethodType, implMethod, instantiatedMethodType, false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);:实例化mfInnerClassLambdaMetafactory对象,这个对象负责创建实现特定接口的函数对象。传入的参数包括调用者的查找上下文、被调用方法的名称和类型、SAM(Single Abstract Method)接口的方法类型、实现方法的方法句柄、以及实例化方法的类型。false表示这个lambda对象不需要是可序列化的,EMPTY_CLASS_ARRAYEMPTY_MT_ARRAY分别表示没有额外的接口和方法类型需要被实现或适配。
    • mf.validateMetafactoryArgs();:调用mfvalidateMetafactoryArgs方法进行参数验证,确保传入的参数满足lambda表达式和方法引用的链接要求。
    • return mf.buildCallSite();:调用mfbuildCallSite方法构建并返回一个CallSite对象,这个对象的目标可以用来执行捕获,生成实现了指定接口的实例。

    CallSite是Java中的一个类,它代表了一个动态方法调用点。在Java7的动态语言支持中,CallSite提供了一种机制,允许方法调用的行为在运行时动态改变,而不是在编译时静态确定。这对于实现动态类型语言或支持某些高级动态特性的静态类型语言(如Java中的lambda表达式和方法引用)非常有用。CallSite对象包含一个称为目标(target)的MethodHandle,这个MethodHandle实际上定义了调用点的行为。当对CallSite进行方法调用时,实际上是在调用其目标MethodHandle
    Java中的CallSite几种不同的类型,包括:

    • MethodHandleNatives.CallSite:这是最基本CallSite,直接关联一个MethodHandle作为其调用目标。
    • ConstantCallSite:一个不可变CallSite,其目标在构造时被设置,并且之后不能改变。这对于那些不需要改变的方法调用非常有用,可以提供更好的性能。
    • MutableCallSite:一个可变CallSite,允许改变其目标MethodHandle。这对于需要根据运行时条件改变调用行为的情况非常有用。
    • VolatileCallSite:类似于MutableCallSite,但是对目标MethodHandle的更新是volatile的,确保了线程安全

    CallSiteMethodHandle是Java对动态语言特性的支持的核心部分,它们使得Java能够以更灵活和动态的方式处理方法调用,支持如lambda表达式和方法引用等现代编程特性。

    metafactory方法返回的调用点是CallSite的一个实例。具体来说,根据LambdaMetafactory的实现,它通常返回的是ConstantCallSite的一个实例ConstantCallSiteCallSite的一个子类,它表示一个不可变的调用点。一旦ConstantCallSite的目标方法句柄(MethodHandle)被设置,它就不会改变。这种特性使得ConstantCallSite非常适合于lambda表达式和方法引用的场景,因为这些场景中的目标方法通常在创建时就已经确定,并且在其生命周期内不需要改变。
    ·
    LambdaMetafactory的上下文中,metafactory方法通过动态生成的类来实现函数接口,并创建一个指向这个实现的方法句柄(MethodHandle)。然后,这个方法句柄被用作ConstantCallSite的目标,从而创建一个CallSite实例。这个CallSite实例在被调用时,会直接调用那个实现了函数接口的动态生成类的方法。

这段代码通过动态创建和配置CallSite对象,支持了Java中lambda表达式和方法引用表达式的动态实现。

2.2、InnerClassLambdaMetafactory

InnerClassLambdaMetafactory构造函数、buildCallSite构建CallSite调用点

 /**
	 * 构造函数:创建一个内部类Lambda元工厂的实例。
	 * 该构造函数用于支持标准情况以及允许序列化或桥接等不常见选项。
	 *
	 * @param caller 由VM自动堆叠;代表具有调用者访问权限的查找上下文。
	 * @param invokedType 由VM自动堆叠;被调用方法的签名,包括返回的lambda对象的预期静态类型,
	 *                    以及lambda捕获参数的静态类型。如果实现方法是实例方法,调用签名的第一个参数将对应于接收者。
	 * @param samMethodName 转换为lambda或方法引用的函数接口中的方法名称,表示为String。
	 * @param samMethodType 转换为lambda或方法引用的函数接口中的方法类型,表示为MethodType。
	 * @param implMethod 应当被调用的实现方法(适当调整参数类型、返回类型和捕获参数后),当调用结果函数接口实例的方法时。
	 * @param instantiatedMethodType 在从捕获站点实例化类型变量后,主要函数接口方法的签名。
	 * @param isSerializable lambda是否应该是可序列化的?如果设置,目标类型或一个附加的SAM类型必须扩展{@code Serializable}。
	 * @param markerInterfaces lambda对象应该实现的附加接口。
	 * @param additionalBridges 额外的签名,这些签名将被桥接到实现方法。
	 * @throws LambdaConversionException 如果违反了元工厂协议的任何不变量。
	 */
    public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
                                       MethodType invokedType,
                                       String samMethodName,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType,
                                       boolean isSerializable,
                                       Class<?>[] markerInterfaces,
                                       MethodType[] additionalBridges)
            throws LambdaConversionException {
        // 调用父类构造函数,初始化基本参数
	    super(caller, invokedType, samMethodName, samMethodType,
	          implMethod, instantiatedMethodType,
	          isSerializable, markerInterfaces, additionalBridges);
	    // 初始化实现方法的类名,将'.'替换为'/'
	    implMethodClassName = implDefiningClass.getName().replace('.', '/');
	    // 初始化实现方法的名称
	    implMethodName = implInfo.getName();
	    // 初始化实现方法的描述符
	    implMethodDesc = implMethodType.toMethodDescriptorString();
	    // 初始化实现方法返回类型的类
	    implMethodReturnClass = (implKind == MethodHandleInfo.REF_newInvokeSpecial)
	            ? implDefiningClass
	            : implMethodType.returnType();
	    // 初始化生成类构造函数的类型
	    constructorType = invokedType.changeReturnType(Void.TYPE);
	    // 生成并初始化lambda类的名称
	    lambdaClassName = targetClass.getName().replace('.', '/') + "$$Lambda$" + counter.incrementAndGet();
	    // 【重要⭐️⭐️⭐️⭐️⭐️】初始化ASM类写入器
	    cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
	    // 初始化构造函数参数名称和描述符数组
	    int parameterCount = invokedType.parameterCount();
	    if (parameterCount > 0) {
	       // 初始化参数名和参数描述数组,大小为方法参数的数量
			argNames = new String[parameterCount];
			argDescs = new String[parameterCount];
			// 遍历所有参数,生成参数名和参数描述
			for (int i = 0; i < parameterCount; i++) {
			    // 为每个参数生成一个唯一的名称,格式为"arg$序号"
			    argNames[i] = "arg$" + (i + 1);
			    // 使用BytecodeDescriptor工具类将参数类型转换为字符串描述形式
			    argDescs[i] = BytecodeDescriptor.unparse(invokedType.parameterType(i));
			}

	    } else {
	        // 当调用类型参数计数为0时,初始化参数名和参数描述数组为空字符串数组
			argNames = argDescs = EMPTY_STRING_ARRAY;
	    }
    }


   /**
	 * 构建CallSite。生成实现功能接口的类文件,定义类,如果没有参数则创建类的实例,
	 * 该实例将由CallSite返回,否则,生成的句柄将调用类的构造函数。
	 *
	 * @return CallSite,调用时,将返回一个功能接口的实例
	 * @throws ReflectiveOperationException 反射操作异常
	 * @throws LambdaConversionException 如果没有找到正确形式的功能接口
	 */
    @Override
    CallSite buildCallSite() throws LambdaConversionException {
         // 生成实现了函数接口的内部类
	    final Class<?> innerClass = spinInnerClass();
	    // 如果调用类型没有参数,即无需捕获的变量
	    if (invokedType.parameterCount() == 0) {
	        // 通过反射获取一个内部类的所有构造函数,并在只有一个构造函数的情况下,将这个唯一的构造函数设置为可访问的。
	        final Constructor<?>[] ctrs = AccessController.doPrivileged(
	                new PrivilegedAction<Constructor<?>[]>() {
	            @Override
	            public Constructor<?>[] run() {
	            	// 返回了innerClass(内部类)的所有构造函数,包括私有的。
	                Constructor<?>[] ctrs = innerClass.getDeclaredConstructors();
	                if (ctrs.length == 1) {
	                    // 如果只有一个构造函数,设置为可访问
	                    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();
	            // 创建并返回一个持有lambda对象的ConstantCallSite
	            return new ConstantCallSite(MethodHandles.constant(samBase, inst));
	        }
	        catch (ReflectiveOperationException e) {
	            throw new LambdaConversionException("Exception instantiating lambda object", e);
	        }
	    } else {
	        // 如果有参数,需要通过静态方法来创建CallSite
	        try {
	            // 确保类已经被完全初始化
	            UNSAFE.ensureClassInitialized(innerClass);
	            // 查找静态方法并创建CallSite
	            return new ConstantCallSite(
	                    MethodHandles.Lookup.IMPL_LOOKUP
	                         .findStatic(innerClass, NAME_FACTORY, invokedType));
	        }
	        catch (ReflectiveOperationException e) {
	            throw new LambdaConversionException("Exception finding constructor", e);
	        }
	    }
    }

/**
	 * 生成并返回一个实现了功能接口的类文件。
	 *
	 * @implNote 生成的类文件不包含SAM方法可能存在的异常签名信息,
	 * 旨在减少类文件大小。这是无害的,因为已检查的异常会被擦除,
	 * 没有人会针对这个类文件进行编译,我们不保证lambda对象的反射属性。
	 *
	 * @return 实现了功能接口的类
	 * @throws LambdaConversionException 如果没有找到正确形式的功能接口
	 */
	private Class<?> spinInnerClass() throws LambdaConversionException {
		// 构建一个字符串数组 interfaces,该数组包含了要实现的接口的内部名称(即将.替换为/的全限定类名),同时确保没有重复的接口,并检查是否意外地实现了 Serializable 接口。
	    String[] interfaces;
	    // 获取函数式接口的内部名称,将.替换为/。
	    String samIntf = samBase.getName().replace('.', '/');
	    // 检查基础函数式接口是否意外实现了 Serializable 接口。
	    boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(samBase);
	    // 如果没有额外的标记接口,直接使用函数式接口的内部名称作为 interfaces 的唯一元素。
	    if (markerInterfaces.length == 0) {
	        interfaces = new String[]{samIntf};
	    } else {
	        // 如果 markerInterfaces 非空,确保没有重复的接口(ClassFormatError),使用 LinkedHashSet 来存储接口名称,确保不会有重复。
	        Set<String> itfs = new LinkedHashSet<>(markerInterfaces.length + 1);
	        // 将函数式接口的内部名称添加到集合中
	        itfs.add(samIntf);
	        // 遍历额外的标记接口,将它们的内部名称添加到集合中,并检查是否意外实现了 Serializable 接口。
	        for (Class<?> markerInterface : markerInterfaces) {
	            itfs.add(markerInterface.getName().replace('.', '/'));
	            accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(markerInterface);
	        }
	        // 将接口名称集合转换为字符串数组。
	        interfaces = itfs.toArray(new String[itfs.size()]);
	    }
		/**
		cw 是 ClassWriter 的实例,它是 ASM(一个通用的 Java 字节码操作和分析框架)库中的一个类。ClassWriter 用于动态生成类或接口的二进制字节码。在上下文中,cw 被用来构建和定义一个新的类,这个类是在运行时动态生成的,用于实现特定的功能接口,通常是为了支持 Java 中的 lambda 表达式。
通过调用 ClassWriter 的方法,如 visit、visitMethod 和 visitField,可以分别定义类的基本信息、方法和字段。最终,通过调用 cw.toByteArray() 方法,可以获取到这个动态生成的类的字节码数组,这个数组可以被加载到 JVM 中,从而创建出一个新的类实例。
		*/
		// 定义了一个类,这个类是final和synthetic的,继承自Object类,并实现了interfaces数组中指定的接口。lambdaClassName是这个类的名称。
		// 其中:
		// 	ACC_FINAL 表示这个类是final的
		//	ACC_SYNTHETIC 表示这个类是synthetic的,synthetic标记表明这个类是由编译器自动生成的,而非直接来自源代码。
		//	lambdaClassName 是动态生成的类名
	    cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC,
	             lambdaClassName, null,
	             JAVA_LANG_OBJECT, interfaces);
	
	    // 生成构造函数中要填充的最终字段
	    for (int i = 0; i < argDescs.length; i++) {
	    	// 生成一个private final字段来存储这些参数的值。
	        FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL,
	                                        argNames[i],
	                                        argDescs[i],
	                                        null, null);
	        /**
				这行代码的作用是结束一个字段的访问。在ASM中,每当开始定义一个新的字段时,都会通过调用visitField方法返回一个FieldVisitor对象,通过这个对象可以定义字段的属性。当字段的定义结束时,需要调用visitEnd方法来标志这个过程的结束。
			*/                                
	        fv.visitEnd();
	    }
		// 生成构造函数
	    generateConstructor();
		// 判断是检查invokedType(lambda表达式的目标类型)是否有参数。
	    if (invokedType.parameterCount() != 0) {
	    	// 这个方法的作用是生成工厂方法。工厂方法是一个特殊的方法,用于动态生成并返回实现了函数式接口的类的实例。这个过程通常涉及到字节码的生成和类的加载。
	        generateFactory();
	    }
	
	    /**
		    这行代码通过调用 ClassWriter 的 visitMethod 方法创建了一个新的方法。这个方法的访问级别是 public,方法名是 samMethodName,这是一个从外部传入的参数,表示要实现的SAM接口中的方法名。samMethodType.toMethodDescriptorString() 将方法的签名转换为字符串形式,用于定义方法的参数类型和返回类型。最后两个 null 参数分别表示这个方法的签名和异常,这里不使用这些高级特性,所以传入 null。
		*/
	    MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, samMethodName,
	                                      samMethodType.toMethodDescriptorString(), null, null);
	    // 这行代码给刚才创建的方法添加了一个注解 LambdaForm$Hidden。这个注解是内部使用的,用于标记这个方法不应该被外部调用或者看到。true 参数表示这个注解是在运行时可见的。
	    mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
	    // 这行代码实际上是生成方法体的关键步骤。它创建了一个 ForwardingMethodGenerator 对象,这个对象负责生成方法体的字节码。generate 方法接受一个 MethodType 对象 samMethodType 作为参数,这个对象描述了SAM接口方法的参数类型和返回类型。generate 方法根据这个信息,动态生成字节码,这些字节码实现了将调用转发到实际的目标方法上。
	    new ForwardingMethodGenerator(mv).generate(samMethodType);
	
	
	    /**
			这段代码的主要作用是为了生成桥接方法(Bridge Methods),这些方法用于处理泛型擦除后的类型不匹配问题。在Java中,泛型信息在编译时会被擦除,而桥接方法则用于在运行时保持类型的正确性。这段代码是在动态生成的类中添加这些桥接方法的过程。
	    */
	    // additionalBridges 是一个包含了需要生成桥接方法的 MethodType 对象的数组。
	    if (additionalBridges != null) {
	        for (MethodType mt : additionalBridges) {
	        	// 为每个桥接方法类型生成方法:通过调用 cw.visitMethod 方法生成桥接方法。这里的 cw 是一个 ClassWriter 对象,用于动态生成类的字节码。ACC_PUBLIC|ACC_BRIDGE 是方法的访问标志,表示这是一个公开的桥接方法。samMethodName 是要实现的函数式接口的方法名,mt.toMethodDescriptorString() 将方法类型转换为方法描述符字符串,用于指定方法的签名。
	            mv = cw.visitMethod(ACC_PUBLIC|ACC_BRIDGE, samMethodName,
	                                mt.toMethodDescriptorString(), null, null);
				// 添加方法注解:通过调用 mv.visitAnnotation 方法为生成的桥接方法添加注解。这里的注解是 "Ljava/lang/invoke/LambdaForm$Hidden;",表示这个方法是由lambda表达式生成的,不应该被直接调用。
	            mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
	            // 生成方法体:通过创建一个新的 ForwardingMethodGenerator 对象并调用其 generate 方法来生成桥接方法的方法体。这个方法体基本上是将调用转发到实际的实现方法上。
	            new ForwardingMethodGenerator(mv).generate(mt);
	        }
	    }
	
		/**
			这段代码的作用是根据是否需要序列化,生成对应的方法。具体来说,如果需要生成的类是可序列化的,则生成序列化友好的方法;如果不是故意的可序列化(即无意中成为可序列化的),则生成序列化敌对的方法。最后,调用 cw.visitEnd() 来完成类的定义。

				1.判断是否需要序列化:通过 if (isSerializable) 判断,如果 isSerializable 为 true,则表示需要生成的类是可序列化的,此时会调用 generateSerializationFriendlyMethods() 方法生成序列化友好的方法。
				2.判断是否无意中成为可序列化:如果 isSerializable 为 false,则进入 else if (accidentallySerializable) 判断,accidentallySerializable 为 true 表示类无意中成为了可序列化的(例如,通过实现了某个可序列化的接口)。此时会调用 generateSerializationHostileMethods() 方法生成序列化敌对的方法,这可能是为了避免序列化带来的潜在问题或性能影响。
				3.完成类的定义:无论是否需要序列化,最后都会执行 cw.visitEnd(),这是ASM库中的方法,用于完成类的定义。这一步是生成类文件的最后一步,标志着类定义的结束。
		*/
	    if (isSerializable)
	        generateSerializationFriendlyMethods();
	    else if (accidentallySerializable)
	        generateSerializationHostileMethods();
	
	    cw.visitEnd();
	
		// 这行代码调用 ClassWriter 对象的 toByteArray 方法,将动态生成的类转换为字节码数组。cw 是 ClassWriter 的实例,它负责生成类的字节码。
	    final byte[] classBytes = cw.toByteArray();
	
		/**
			这段代码首先检查 dumper 对象是否为 null。dumper 是一个可能用于将字节码写入文件的工具对象。如果 dumper 不为 null,则执行以下步骤:
				1.使用 AccessController.doPrivileged 方法执行一个特权操作。这是因为写入文件可能需要特定的权限,特别是在启用了安全管理器的环境中。
				2.在 doPrivileged 方法中,执行一个 PrivilegedAction,其 run 方法调用 dumper.dumpClass 方法,将类名 lambdaClassName 和字节码数组 classBytes 传递给它,以便将字节码写入文件。
				3.doPrivileged 方法的第二个参数是 null,表示不使用特定的 AccessControlContext。
				4.第三和第四个参数是 FilePermission 和 PropertyPermission 对象,分别授予读写所有文件的权限和读取用户当前目录的权限。这些权限是执行文件写入操作所必需的。
		*/
	    // 转储到文件
	    if (dumper != null) {
	        AccessController.doPrivileged(new PrivilegedAction<Void>() {
	            @Override
	            public Void run() {
	                dumper.dumpClass(lambdaClassName, classBytes);
	                return null;
	            }
	        }, null,
	        new FilePermission("<<ALL FILES>>", "read, write"),
	        // 创建目录可能需要它
	        new PropertyPermission("user.dir", "read"));
	    }
	    /**
	    	1.代码的作用
				下面这段代码的作用是在运行时动态定义一个匿名类。UNSAFE.defineAnonymousClass 方法接收三个参数:目标类(targetClass),类的字节码(classBytes),以及与类相关联的常量池补丁(这里传入的是 null)。
			2.代码的结构和逻辑
				2.1 targetClass:这是一个 Class 对象,表示新定义的匿名类将与之关联的上下文。通常,这个类是匿名类逻辑上的“宿主”类。
				2.2 classBytes:这是一个字节数组,包含了新匿名类的字节码。这些字节码通常是通过某种字节码生成库(如ASM)动态生成的。
				2.3 null:这个参数是用于类定义时的常量池补丁,这里传入 null 表示不需要进行常量池的补丁。
			3.关键代码块或语句的解释
				3.1 UNSAFE:这是 sun.misc.Unsafe 类的一个实例。Unsafe 类提供了一组底层、危险的操作,通常不推荐在标准Java代码中使用。但在某些特殊场景下,如动态类生成、低级并发控制等,Unsafe 提供的功能是必需的。
				3.2 .defineAnonymousClass(targetClass, classBytes, null):这个方法调用是动态定义匿名类的关键。它将 classBytes 中的字节码转换为一个Java类,并将这个新类与 targetClass 关联起来。由于这个类是匿名的,它没有正式的类名。传入的 null 参数表示在定义类的过程中不需要对常量池进行任何补丁操作。
	    */
	
		// 通过 Unsafe 类的 defineAnonymousClass 方法动态定义了一个匿名类,这个类的字节码由 classBytes 提供,而这个匿名类在逻辑上与 targetClass 关联。
	    return UNSAFE.defineAnonymousClass(targetClass, classBytes, null);
	}

四、利用java.lang.invoke.InnerClassLambdaMetafactory#dumper 转储lambda文件

// 静态初始化块,用于初始化dumper
	static {
	    /**
		 * 获取并设置代理类转储功能
		 */
		// 定义系统属性的键名,用于控制是否转储内部lambda代理类
		final String key = "jdk.internal.lambda.dumpProxyClasses";
		
		// 使用AccessController执行特权操作,获取系统属性值
		String path = AccessController.doPrivileged(
		        new GetPropertyAction(key), // 创建获取属性的动作
		        null, // 不指定AccessControlContext
		        new PropertyPermission(key , "read") // 指定所需的权限
		);

		// 根据获取的路径创建ProxyClassesDumper实例
		// 如果路径为null,则不启用转储功能
		dumper = (null == path) ? null : ProxyClassesDumper.getInstance(path);

	}

JVM参数:jdk.internal.lambda.dumpProxyClasses
命令:java -Djdk.internal.lambda.dumpProxyClasses ClassName
转储得到内部类
反编译:java -jar cfr-0.152.jar LambdaExample.class --decodelambdas false

步骤一:源码

import java.util.Arrays;
import java.util.List;

public class LambdaExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("Apple", "Banana", "Cherry");
        items.forEach(item -> System.out.println(item));
    }
}

步骤二:编译,生成LambdaExample.class 文件

javac LambdaExample.java

步骤三:执行java命令,生成文件:LambdaExample$$Lambda$1.class

java -Djdk.internal.lambda.dumpProxyClasses LambdaExample

这个命令是用来调试和分析 Java 中 lambda 表达式的底层实现的。具体解释如下:

  1. -D 参数:
    用于设置系统属性。

  2. jdk.internal.lambda.dumpProxyClasses
    这是一个特殊的系统属性,用于指示 JVM 将 lambda 表达式生成的代理类保存到磁盘。

  3. LambdaExample
    这是要运行的包含 lambda 表达式的 Java 类名。

当你运行这个命令时,JVM 会执行以下操作:

  1. 运行 LambdaExample 类。
  2. 对于该类中的每个 lambda 表达式,JVM 会生成一个代理类。
  3. 这些生成的代理类会被保存到磁盘上,通常在当前工作目录下。

这个功能主要用于:

  • 分析 lambda 表达式的底层实现
  • 调试复杂的 lambda 表达式
  • 了解 JVM 如何处理和优化 lambda 表达式

生成的代理类文件名通常遵循这样的模式:
主类名$Lambda$序号.class

步骤四:生成反编译代码:lambda内部类

java -jar cfr-0.152.jar 'LambdaExample$$Lambda$1.class' --decodelambdas false

mac电脑,此处LambdaExample$$Lambda$1.class需要带引号,因为在命令行中,$ 是一个特殊字符,用于引用变量。在这个上下文中,$$ 容易被解释为当前 shell 进程的 PID(进程ID),而不是文件名的一部分。所以你需要用引号将文件名括起来,这样可以防止 shell 解释 $ 字符。

/*
 * Decompiled with CFR 0.152.
 */
import java.lang.invoke.LambdaForm;
import java.util.function.Consumer;

final class LambdaExample$$Lambda$1
implements Consumer {
    private LambdaExample$$Lambda$1() {
    }

    @LambdaForm.Hidden
    public void accept(Object object) {
        LambdaExample.lambda$main$0((String)object);
    }
}

这段代码是由Java编译器为lambda表达式生成的内部类。让我们逐部分解析:

  1. final class LambdaExample$$Lambda$1

    • 这是一个自动生成的内部类,名称中的 $$Lambda$1 表示它是为第一个lambda表达式生成的。
    • final 关键字表示这个类不能被继承。
  2. implements Consumer

    • 这个类实现了 Consumer 接口,这是Java 8引入的函数式接口之一。
  3. private LambdaExample$$Lambda$1()

    • 这是一个私有构造函数,防止外部直接实例化这个类。
  4. @LambdaForm.Hidden

    • 这是一个内部注解,用于标记这个方法不应该在堆栈跟踪中显示。
  5. public void accept(Object object)

    • 这是 Consumer 接口中定义的方法。
    • 方法接受一个 Object 类型的参数。
  6. LambdaExample.lambda$main$0((String)object);

    • 这行代码调用了 LambdaExample 类中的一个静态方法 lambda$main$0
    • 参数 object 被强制转换为 String 类型。

这个生成的类实际上是lambda表达式的一个"包装器"。它将lambda表达式封装成一个实现了 Consumer 接口的具体类。当lambda表达式被调用时,它会调用 LambdaExample 类中相应的静态方法(在这里是 lambda$main$0)。

这种实现方式允许Java在不使用匿名内部类的情况下支持lambda表达式,从而提高了性能和减少了内存使用。

三、结论

lambda 底层实现机制
1.lambda 表达式的本质:函数式接口的匿名子类的匿名对象
2.lambda表达式是语法糖

语法糖:编码时是lambda简洁的表达式,在字节码期,语法糖会被转换为实际复杂的实现方式,含义不变;即编码表面有个糖衣,在编译期会被脱掉

Lambda表达式的编译及运行过程如下:

在这里插入图片描述

编译阶段

  1. Lambda表达式识别

    • 编译器识别Lambda表达式,将其转换为静态方法。
  2. 生成invokedynamic指令

    • 编译器为每个Lambda表达式生成一个invokedynamic指令。
    • 指定LambdaMetafactory.metafactory或altMetafactory作为引导方法。
  3. ASM使用

    • 编译器可能使用ASM库生成或修改字节码。

运行阶段

  1. 引导方法(Bootstrap Method)调用

    • 当JVM首次遇到某个invokedynamic指令时,它会调用指定的引导方法。对于Lambda表达式,这个引导方法通常是LambdaMetafactorymetafactory方法。
  2. LambdaMetafactory调用

    • 引导方法(通常是LambdaMetafactory.metafactory)被调用。
    • 接收参数:MethodHandles.Lookup、函数式接口信息、Lambda方法信息等。
  3. 创建CallSite对象

    • 引导方法的任务之一是创建一个CallSite对象。CallSite是一个抽象类,它代表了一个动态方法调用点。它的具体实现类(如ConstantCallSite)封装了对特定方法的调用。
    • CallSite对象持有一个MethodHandle,这个MethodHandle指向实际要执行的方法。对于Lambda表达式,这个方法是Lambda表达式转换成的方法。
  4. 绑定MethodHandle

    • 在创建CallSite对象时,引导方法会根据Lambda表达式的目标类型和实际代码,构造一个MethodHandle。这个MethodHandle直接指向了包含Lambda表达式代码的方法。
    • 然后,这个MethodHandle被绑定到CallSite对象上。这意味着,当通过这个CallSite调用方法时,实际上是通过绑定的MethodHandle来调用Lambda表达式对应的方法。
  5. 返回CallSite对象

    • 引导方法返回CallSite对象给JVM。这个CallSite对象随后被用于所有对该invokedynamic指令的调用。
    • 由于CallSite对象已经绑定了对应的MethodHandle,因此每次通过这个CallSite调用方法时,都会直接调用到Lambda表达式对应的方法,无需再次解析。
  6. InnerClassLambdaMetafactory使用
    InnerClassLambdaMetafactory用于动态生成实现函数式接口的类。这个过程主要通过spinInnerClass方法实现。

  7. spinInnerClass方法
    spinInnerClass方法的主要任务是动态生成一个类,这个类实现了指定的函数式接口,并包含了Lambda表达式的代码。这个方法通过直接操作字节码来创建类,通常使用ASM库来完成。

  8. 使用Unsafe生成匿名类
    spinInnerClass方法可能会通过Unsafe类的功能来加载生成的字节码。Unsafe是JDK内部的一个类,提供了一些底层操作,比如直接内存访问、线程调度等。其中,Unsafe.defineAnonymousClass方法可以用来加载一个类的字节码,并返回这个类的Class对象。这个方法允许动态生成的类没有对应的.class文件。

  9. 生成匿名类的过程

    1. 生成字节码

      • spinInnerClass方法使用ASM库生成实现了函数式接口的类的字节码。这个类包含了Lambda表达式的实现代码。
    2. 加载类

      • 使用Unsafe.defineAnonymousClass方法加载生成的字节码。这个方法接受三个参数:父类的Class对象、字节码数组、以及与类相关的常量池补丁。这个方法返回新加载的类的Class对象。
    3. 实例化

      • 通过反射或其他机制,使用返回的Class对象创建实例。这个实例实现了指定的函数式接口,并包含了Lambda表达式的代码。
  10. 绑定到CallSite

    • 创建一个MethodHandle,指向新生成的类的实例方法。这个MethodHandle随后被绑定到CallSite对象上,用于后续的方法调用。

注意

  • Unsafe类的使用通常不推荐,因为它提供了很多强大但危险的底层操作。在JDK 9及以后版本中,Unsafe类的一些功能被限制或替换,以促进更安全的编程实践。
  • JDK的具体实现细节可能会随着版本变化。上述过程主要描述了一种通过Unsafe加载动态生成类的方法,但实际的实现可能会有所不同。
  1. Lambda表达式执行
  • 当调用Lambda表达式时,通过CallSite间接调用动态生成的类中的方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2181506.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决磁盘负载不均——ElasticSearch 分片分配和路由设置

ES 分片分配&#xff08;Shard Allocation&#xff09;时间点&#xff1a; 初始恢复&#xff08;Initial Recovery&#xff09;副本分配&#xff08;Replica Allocation&#xff09;重平衡&#xff08;Rebalance&#xff09;节点添加或移除 小结&#xff1a; 准备移除节点时&a…

Flask-2

文章目录 请求全局钩子[hook]异常抛出和捕获异常abort 主动抛出HTTP异常errorhandler 捕获错误 context请求上下文(request context)应用上下文(application context)current_appg变量 两者区别&#xff1a; 终端脚本命令flask1.0的终端命令使用自定义终端命令 flask2.0的终端命…

25中国烟草校园招聘面试问题总结 烟草面试全流程及面试攻略

开头附上工作招聘面试必备问题噢~~包括综合面试题、无领导小组面试题资源文件免费&#xff01;全文干货。 工作招聘无领导小组面试全攻略最常见面试题&#xff08;第一部分&#xff09;共有17章可用于国企私企合资企业工作招聘面试面试必备心得面试总结资源-CSDN文库https://d…

.NET CORE程序发布IIS后报错误 500.19

发布IIS后浏览时报错误500.19&#xff0c;同时配置文件web.config的路径中也存在问号“?”。 可能原因&#xff1a;没有安装运行时

树和二叉树知识点大全及相关题目练习【数据结构】

树和二叉树 要注意树和二叉树是两个完全不同的结构、概念&#xff0c;它们之间不存在包含之类的关系 树的定义 树&#xff08;Tree&#xff09;是n&#xff08;n≥0&#xff09;个结点的有限集&#xff0c;它或为空树&#xff08;n 0&#xff09;&#xff1b;或为非空树&a…

WPF 设计属性 设计页面时实时显示 页面涉及集合时不显示处理 设计页面时显示集合样式 显示ItemSource TabControl等集合样式

WPF 设计属性 设计页面时实时显示 页面涉及集合时不显示处理 设计页面时显示集合样式 显示ItemSource TabControl等集合样式 1、设计显示属性 1、设计时显示属性依赖以下属性 xmlns:d"http://schemas.microsoft.com/expression/blend/2008"2、在运行时不显示设计属性…

健康生活,从日常细节开始

健康生活&#xff0c;从日常细节开始 在快节奏的现代生活中&#xff0c;健康养生似乎成了一种奢侈的追求。但殊不知&#xff0c;真正的养生之道&#xff0c;往往就蕴含在我们日常的点点滴滴之中。今天&#xff0c;就让我们一起探讨几个简单却极易被忽视的健康生活小贴士&#…

828华为云征文|华为云 Flexus X 实例初体验

一直想有自己的一款的服务器&#xff0c;为了更好的进行家庭娱乐&#xff0c;甚至偶尔可以满足个人搭建开发环境的需求&#xff0c;直到接触到了华为云 Flexus X 云服务器。Flexus 云服务器 X 实例是面向中小企业和开发者打造的轻量级云服务器。提供快速应用部署和简易的管理能…

Electrodoc 5.2 专业电工工具集合,支持多种计算器和资料查询!

ElectroDroid Pro 是一款专业的电工工具集合应用&#xff0c;支持多种计算器和资料查询功能。包括功率计算器、分贝转换器、频率转换器、模数转换器等。还提供了端口引脚定义、资料查询等功能&#xff0c;支持 EIA 标准电阻系列。使用方法如下&#xff1a;1. 下载并安装 Electr…

达梦8-数据守护集群主备故障实验和脑裂处理

实验1&#xff1a;将内网断开&#xff0c;查看主备库状态&#xff0c;并测试数据同步情况 测试环境 ##主库信息 内网IP-[MAL_HOST 192.168.50.100] 外网IP-[MAL_INST_HOST 192.168.101.11] 主库实例名-[DM01] ##备库信息 内网IP-[MAL_HOST 192.168.50.110] 外网IP-[MAL_INS…

如何在C语言中实现Doris异步执行Insert语句

如何在C语言中实现Doris异步执行Insert语句 Doris(原名Apache Doris)是一个现代化的MPP(Massively Parallel Processing)分析型数据库,适用于超大规模数据的实时查询和分析。为了在C语言中实现向Doris数据库异步插入数据,我们需要解决以下几个关键问题: 设置Doris客户端…

认识动态规划算法和实践(java)

前言 动态规划算法里面最有意思的一个东西之一。动态规划初学肯定会有一定晦涩难懂。如果我们去网上搜索&#xff0c;动态规划的资料&#xff0c;它一开始都是将很多的理论&#xff0c;导致会认为很难&#xff0c;但是这个东西实际上是有套路的。 动态规划的英语是Dynamic Pr…

Java 死锁及避免讲解和案例示范

在大型分布式系统中&#xff0c;死锁是一种常见但难以排查的并发问题。特别是在 Java 领域&#xff0c;死锁问题可能导致系统崩溃或卡顿。本文将以电商交易系统为例&#xff0c;详细讲解如何识别和避免 Java 程序中的死锁问题&#xff0c;确保系统高效运行。 1. 什么是死锁&am…

如何初步部署自己的服务器,达到生信分析的及格线2(待更新)

参考我的上一篇博客https://blog.csdn.net/weixin_62528784/article/details/142621762?spm1001.2014.3001.5501&#xff0c; 现在我们已经有了一个能够跑一些基础任务的、基本没有配置的服务器了&#xff0c;接下来要做的任务就是&#xff1a; &#xff08;1&#xff09;进一…

centos一些常用命令

文章目录 查看磁盘信息使用 df 命令使用 du 命令 查看磁盘信息 使用 df 命令 df&#xff08;disk free&#xff09;命令用于显示文件系统的磁盘空间占用情况。 查看所有挂载点的磁盘使用情况&#xff1a; df -h选项说明&#xff1a; -h 参数表示以人类可读的格式&#xff0…

开发微信小程序 基础02

WX模板 1.对比 ①标签名称不同 ②属性节点不同 ③提供类似vue的模板语法 2.模板语法 2.1数据动态绑定 2.1.1在data种定义数据 在页面对应的.js文件中&#xff0c;把数据定义到data对象中即可 例---data &#xff1a; { info : init data , msList : [{msg : hello}, { ms…

开发微信小程序 基础03

WXSS(类似CSS) 定义&#xff1a; WXSS (WeiXin Style Sheets)是一套样式语言&#xff0c;用于描述 WXML的组件样式&#xff0c;类似于网页开发中的 CSS。 分类&#xff1a; 全局样式&#xff1a;定义在 app.wxss 中的样式为全局样式&#xff0c;作用于每一个页面 局部样式&…

解决 Android WebView 无法加载 H5 页面常见问题的实用指南

目录 1. WebView 简介 2. 常见问题 3. 网络权限设置 4. 启用 JavaScript 5. DOM Storage 的重要性 6. 处理 HTTPS 问题 7. 设置 WebViewClient 8. 调试工具 9. 其他调试技巧 10. 结论 相关推荐 1. WebView 简介 Android WebView 是一种视图组件&#xff0c;使得 And…

基于SSM+小程序的电影院订票选座管理系统(电影2)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM的电影院订票选座小程序管理系统实现了管理员和用户二个角色。管理员实现了用户管理、影院信息管理、电影类型管理、电影信息管理、系统管理、订单管理等。用户实现了影院信息、电…

【论文笔记】Flamingo: a Visual Language Model for Few-Shot Learning

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Flamingo: a Visual Langu…