lambda表达式底层实现:反编译LambdaMetafactory + 转储dump + 运行过程 + 反汇编 + 动态指令invokedynamic

一、结论先行

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

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

二、结论证明

2.1 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.2 反编译代码详解

2.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.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 如果没有找到正确形式的功能接口*/@OverrideCallSite buildCallSite() throws LambdaConversionException {// 生成实现了函数接口的内部类final Class<?> innerClass = spinInnerClass();// 如果调用类型没有参数,即无需捕获的变量if (invokedType.parameterCount() == 0) {// 通过反射获取一个内部类的所有构造函数,并在只有一个构造函数的情况下,将这个唯一的构造函数设置为可访问的。final Constructor<?>[] ctrs = AccessController.doPrivileged(new PrivilegedAction<Constructor<?>[]>() {@Overridepublic 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对象的ConstantCallSitereturn new ConstantCallSite(MethodHandles.constant(samBase, inst));}catch (ReflectiveOperationException e) {throw new LambdaConversionException("Exception instantiating lambda object", e);}} else {// 如果有参数,需要通过静态方法来创建CallSitetry {// 确保类已经被完全初始化UNSAFE.ensureClassInitialized(innerClass);// 查找静态方法并创建CallSitereturn 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>() {@Overridepublic 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);}

2.3 dumper 转储lambda文件

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

// 静态初始化块,用于初始化dumperstatic {/*** 获取并设置代理类转储功能*/// 定义系统属性的键名,用于控制是否转储内部lambda代理类final String key = "jdk.internal.lambda.dumpProxyClasses";// 使用AccessController执行特权操作,获取系统属性值String path = AccessController.doPrivileged(new GetPropertyAction(key), // 创建获取属性的动作null, // 不指定AccessControlContextnew 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

2.3.1 步骤一:源码

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

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

javac LambdaExample.java

2.3.3 步骤三:执行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

2.3.4 步骤四:生成反编译代码: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.Hiddenpublic 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表达式,从而提高了性能和减少了内存使用。

2.4、Lambda表达式的编译及运行过程

在这里插入图片描述

2.4.1 编译阶段

  1. Lambda表达式识别

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

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

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

2.4.2 运行阶段

  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间接调用动态生成的类中的方法。

2.5 反汇编

2.5.1 反汇编字节码

源码行号,汇编字节码会显示映射行号关系
在这里插入图片描述

javap -p -v LambdaExample.class 反汇编命令会显示 LambdaExample 类的详细字节码信息,包括私有成员和方法、invokedynamic 指令。

javap -p -v LambdaExample.class

反汇编字节码

Classfile /Users/wangnan/Desktop/LambdaExample.classLast modified 2024-9-28; size 1247 bytesMD5 checksum 73b886aea60866993c41e7b14ed9fd69Compiled from "LambdaExample.java"
public class LambdaExampleminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Methodref          #12.#23        // java/lang/Object."<init>":()V#2 = Class              #24            // java/lang/String#3 = String             #25            // Apple#4 = String             #26            // Banana#5 = String             #27            // Cherry#6 = Methodref          #28.#29        // java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;#7 = InvokeDynamic      #0:#35         // #0:accept:()Ljava/util/function/Consumer;#8 = InterfaceMethodref #36.#37        // java/util/List.forEach:(Ljava/util/function/Consumer;)V#9 = Fieldref           #38.#39        // java/lang/System.out:Ljava/io/PrintStream;#10 = Methodref          #40.#41        // java/io/PrintStream.println:(Ljava/lang/String;)V#11 = Class              #42            // LambdaExample#12 = Class              #43            // java/lang/Object#13 = Utf8               <init>#14 = Utf8               ()V#15 = Utf8               Code#16 = Utf8               LineNumberTable#17 = Utf8               main#18 = Utf8               ([Ljava/lang/String;)V#19 = Utf8               lambda$main$0#20 = Utf8               (Ljava/lang/String;)V#21 = Utf8               SourceFile#22 = Utf8               LambdaExample.java#23 = NameAndType        #13:#14        // "<init>":()V#24 = Utf8               java/lang/String#25 = Utf8               Apple#26 = Utf8               Banana#27 = Utf8               Cherry#28 = Class              #44            // java/util/Arrays#29 = NameAndType        #45:#46        // asList:([Ljava/lang/Object;)Ljava/util/List;#30 = Utf8               BootstrapMethods#31 = MethodHandle       #6:#47         // 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;#32 = MethodType         #48            //  (Ljava/lang/Object;)V#33 = MethodHandle       #6:#49         // invokestatic LambdaExample.lambda$main$0:(Ljava/lang/String;)V#34 = MethodType         #20            //  (Ljava/lang/String;)V#35 = NameAndType        #50:#51        // accept:()Ljava/util/function/Consumer;#36 = Class              #52            // java/util/List#37 = NameAndType        #53:#54        // forEach:(Ljava/util/function/Consumer;)V#38 = Class              #55            // java/lang/System#39 = NameAndType        #56:#57        // out:Ljava/io/PrintStream;#40 = Class              #58            // java/io/PrintStream#41 = NameAndType        #59:#20        // println:(Ljava/lang/String;)V#42 = Utf8               LambdaExample#43 = Utf8               java/lang/Object#44 = Utf8               java/util/Arrays#45 = Utf8               asList#46 = Utf8               ([Ljava/lang/Object;)Ljava/util/List;#47 = Methodref          #60.#61        // 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;#48 = Utf8               (Ljava/lang/Object;)V#49 = Methodref          #11.#62        // LambdaExample.lambda$main$0:(Ljava/lang/String;)V#50 = Utf8               accept#51 = Utf8               ()Ljava/util/function/Consumer;#52 = Utf8               java/util/List#53 = Utf8               forEach#54 = Utf8               (Ljava/util/function/Consumer;)V#55 = Utf8               java/lang/System#56 = Utf8               out#57 = Utf8               Ljava/io/PrintStream;#58 = Utf8               java/io/PrintStream#59 = Utf8               println#60 = Class              #63            // java/lang/invoke/LambdaMetafactory#61 = NameAndType        #64:#68        // 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;#62 = NameAndType        #19:#20        // lambda$main$0:(Ljava/lang/String;)V#63 = Utf8               java/lang/invoke/LambdaMetafactory#64 = Utf8               metafactory#65 = Class              #70            // java/lang/invoke/MethodHandles$Lookup#66 = Utf8               Lookup#67 = Utf8               InnerClasses#68 = 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;#69 = Class              #71            // java/lang/invoke/MethodHandles#70 = Utf8               java/lang/invoke/MethodHandles$Lookup#71 = Utf8               java/lang/invoke/MethodHandles
{public LambdaExample();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 6: 0public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=4, locals=2, args_size=10: iconst_31: anewarray     #2                  // class java/lang/String4: dup5: iconst_06: ldc           #3                  // String Apple8: aastore9: dup10: iconst_111: ldc           #4                  // String Banana13: aastore14: dup15: iconst_216: ldc           #5                  // String Cherry18: aastore19: invokestatic  #6                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;22: astore_123: aload_124: invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;29: invokeinterface #8,  2            // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V34: returnLineNumberTable:line 8: 0line 9: 23line 10: 34private static void lambda$main$0(java.lang.String);descriptor: (Ljava/lang/String;)Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack=2, locals=1, args_size=10: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_04: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V7: returnLineNumberTable:line 9: 0
}
SourceFile: "LambdaExample.java"
InnerClasses:public static final #66= #65 of #69; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:0: #31 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:#32 (Ljava/lang/Object;)V#33 invokestatic LambdaExample.lambda$main$0:(Ljava/lang/String;)V#34 (Ljava/lang/String;)V

2.5.2 汇编语言解释

2.5.2.1. 类信息
public class LambdaExampleminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER

这些行描述了Java类文件的基本信息,每一行都有特定的含义:

  1. public class LambdaExample:

    • public 表示这个类是公开的,可以被任何其他类访问。
    • class LambdaExample 声明了一个名为 LambdaExample 的类。
  2. minor version: 0:

    • 这表示类文件的次版本号是0。Java类文件格式有主版本号和次版本号,它们共同定义了类文件的版本。次版本号通常用于表示较小的变更。
  3. major version: 52:

    • 这表示类文件的主版本号是52。Java的每个版本都有一个特定的主版本号。例如,主版本号52对应于Java 8。这意味着这个类文件是用Java 8编译的。
  4. flags: ACC_PUBLIC, ACC_SUPER:

    • flags 表示类的访问标志,这些标志提供了类的一些额外信息。
    • ACC_PUBLIC 表示这个类是公开的,可以被任何其他包中的类访问。
    • ACC_SUPER 用于支持某些特定的编译模式。在早期版本的Java中,ACC_SUPER标志被引入以允许更精确的调用超类方法的语义。现在,这个标志对所有新版本的Java类都是必需的,但它实际上不影响现代Java虚拟机的行为。

这些行提供了关于LambdaExample类的基本元数据,包括它的访问级别、编译的Java版本,以及一些关于类行为的标志。

2.5.2.2. 常量池

#1 ~ #71 都是常量池(Constant Pool)的内容。在Java的类文件结构中,常量池是一个表,它包含了类、接口、方法以及字段的符号引用。这些符号引用包括了各种常量,如类和接口的全名、字段的名称和描述符、方法的名称和描述符等。常量池为类文件中的符号引用提供了索引,使得文件可以以一种紧凑的方式存储信息。

常量池中的每一项都可以是以下几种类型之一:

  • #1 ~ #71:是索引号
  • Utf8:UTF-8编码的字符串。
  • Integer:整型字面量。
  • Float:浮点型字面量。
  • Long:长整型字面量。
  • Double:双精度浮点型字面量。
  • Class:类或接口的符号引用。
  • String:字符串类型的字面量。
  • Fieldref:字段的符号引用,包括类或接口的名称以及字段名称和描述符。
  • Methodref:类中方法的符号引用。
  • InterfaceMethodref:接口中方法的符号引用。
  • NameAndType:字段或方法的名称和描述符。
  • MethodHandle:表示方法句柄。
  • MethodType:表示方法类型。
  • InvokeDynamic:表示动态方法调用点。

例如:

  • #1 = Methodref #12.#23:这表示一个方法的符号引用,指向常量池中第12项(一个类或接口)和第23项(一个名称和类型)。
  • #3 = String #25:这表示一个字符串字面量,其值在常量池的第25项中以Utf8形式存储。
  • #7 = InvokeDynamic #0:#35 // #0: accept:()Ljava/util/function/Consumer;:这表示一个动态方法调用点,相关的引导方法和调用点的具体信息存储在常量池的其他位置。
  • #35 = NameAndType #50:#51 // accept:()Ljava/util/function/Consumer; :NameAndType这表示这个常量是一个名称和类型描述符。实际上描述了 java.util.function.Consumer 接口中的 accept 方法。在 Lambda 表达式的上下文中,这个信息被用来创建一个函数式接口的实例。
2.5.2.3 构造函数

这个构造函数的作用是调用超类Object的构造函数来初始化新创建的对象,然后返回。这是Java中所有类默认构造函数的典型行为,如果没有显式定义构造函数,编译器会自动生成这样一个默认构造函数。

public LambdaExample(); // 声明了一个公开的构造函数`LambdaExample`。这是类的默认构造函数,没有参数。descriptor: ()V // 描述符,表示这个方法没有参数(`()`),并且没有返回值(`V`表示void类型)。flags: ACC_PUBLIC // 标志位,`ACC_PUBLIC`表示这个构造函数是公开的,可以被任何类访问。Code: // 下面的部分是这个方法的实际字节码指令。// `stack=1`表示操作数栈的最大深度是1。// `locals=1`表示局部变量表中的变量数量,这里包括`this`引用。//  `args_size=1`表示传入参数的数量,这里只有一个隐式的`this`参数(指向对象自身的引用)。stack=1, locals=1, args_size=10: aload_0 // 将局部变量表中第0个引用类型局部变量(`this`)加载到操作数栈顶。// 调用一个实例方法(特殊处理的方法,如初始化方法`<init>`),`#1`是对常量池的引用,这里指的是`java/lang/Object`的默认构造函数。这条指令的作用是调用超类`Object`的构造函数来初始化当前对象。1: invokespecial #1                  // Method java/lang/Object."<init>":()V4: return // 从方法返回。LineNumberTable: // 这是一个属性,用于映射字节码指令到源代码行号的对应关系,以便于调试。line 6: 0 // 表示字节码中的第0个指令(`aload_0`)对应源代码中的第6行。这意味着构造函数的开始在源代码的第6行。
2.5.2.5 main 方法

这是主方法。它创建了一个包含"Apple"、“Banana”、"Cherry"的字符串数组,将其转换为List,然后使用Lambda表达式遍历这个List。

public static void main(java.lang.String[]);// 声明了一个公开的、静态的main方法,接受一个字符串数组作为参数。descriptor: ([Ljava/lang/String;)V // 方法描述符:接受一个字符串数组([Ljava/lang/String;)作为参数,返回void(V)。flags: ACC_PUBLIC, ACC_STATIC // 方法标志:公开的(ACC_PUBLIC)和静态的(ACC_STATIC)。Code: // 开始方法的字节码指令。stack=4, locals=2, args_size=1 // 操作数栈最大深度为4,局部变量表大小为2,方法参数数量为1。0: iconst_3 // 将整数常量3压入操作数栈。// 创建一个新的String数组,长度为3。1: anewarray     #2                  // class java/lang/String4: dup // 复制栈顶的数组引用。// 9-18. 这几行是在数组中存储"Apple"、"Banana"、"Cherry"字符串://  iconst_0/1/2:压入数组索引。//  ldc #3/4/5:从常量池加载字符串。//  aastore:将字符串存储到数组中。5: iconst_06: ldc           #3                  // String Apple8: aastore9: dup10: iconst_111: ldc           #4                  // String Banana13: aastore14: dup15: iconst_216: ldc           #5                  // String Cherry18: aastore// 调用Arrays.asList方法,将数组转换为List。19: invokestatic  #6                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;22: astore_1 // 将List引用存储到局部变量1中。23: aload_1 // 加载局部变量1(List引用)到栈顶。// 创建Lambda表达式的Consumer实例。24: invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;// 调用List的forEach方法,传入刚创建的Consumer。29: invokeinterface #8,  2            // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V34: return // 方法返回。// 25-27. LineNumberTable: - 源代码行号与字节码指令的对应关系。LineNumberTable:line 8: 0 // 源代码的第8行对应于字节码指令的偏移量0。line 9: 23 // 源代码的第9行对应于字节码指令的偏移量23。line 10: 34 // 源代码的第10行对应于字节码指令的偏移量34。
2.5.2.5 invokedynamic使用:

在下面反汇编的类文件信息中,invokedynamic指令被用于实现lambda表达式:

invokedynamic #7,  0              // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;

这里,invokedynamic指令用于动态生成实现了java.util.function.Consumer接口的对象。这个对象的accept方法体内将调用LambdaExample.lambda$main$0方法,这是lambda表达式的实际执行体。通过使用invokedynamic,JVM在运行时动态生成并绑定lambda表达式的实现,而不是在编译时生成额外的类文件,这样做既减少了资源的消耗,也提高了性能。

2.5.2.6 Lambda方法:

这段字节码定义了一个静态私有方法,该方法由lambda表达式生成,用于接收一个字符串参数并将其打印到控制台。这个方法是编译器自动生成的,用于实现lambda表达式的功能。

// 描述了一个由lambda表达式生成的静态方法 lambda$main$0。
//  这是一个静态(static)方法,名为 lambda$main$0,表示这是由lambda表达式生成的方法。
//	方法接受一个 java.lang.String 类型的参数,并且没有返回值(void)。
//	方法是私有的(private),只能在其所在类内部访问。
private static void lambda$main$0(java.lang.String);descriptor: (Ljava/lang/String;)V // 描述符,表示方法的参数和返回类型。这里 (Ljava/lang/String;)V 表示方法接受一个 String 类型的参数,没有返回值(V 代表 void)// 方法的访问标志。// ACC_PRIVATE 表示方法是私有的。// ACC_STATIC 表示方法是静态的。// ACC_SYNTHETIC 表示这个方法是由编译器自动生成的,不是在源代码中直接定义的。flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode: // 方法体stack=2, locals=1, args_size=1 // stack 表示操作数栈的最大深度。locals 表示局部变量表的大小。args_size 表示传递给方法的参数数量。// 字节码指令// 获取 System.out 静态字段,这是一个 PrintStream 类型的引用。0: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;3: aload_0 // 加载方法的第一个参数(索引为0的局部变量),即传递给方法的 String 对象。// 调用 PrintStream 类的 println 方法,打印传递给方法的 String 参数。4: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V7: return // 方法返回。LineNumberTable:line 9: 0 // 表示源代码中的第9行对应于字节码指令的偏移量0。这有助于调试,使得可以将运行时的异常或行为准确地映射回源代码的行。
2.5.4.7 其他信息

这段代码是Java类文件的一部分,提供了关于类的额外信息,包括源文件名、内部类和引导方法(Bootstrap Methods)的信息。主要作用是为lambda表达式提供运行时支持,通过引导方法(Bootstrap Methods)机制,JVM可以在运行时动态生成和调用由lambda表达式表示的方法。这是Java 7中引入的 invokedynamic 指令的一部分,旨在提高Java对动态语言的支持

SourceFile: "LambdaExample.java" // 这一行指明了这个类文件是从哪个源文件编译而来的,这里是 LambdaExample.java。
InnerClasses:// 这一行描述了一个内部类的信息。这里,MethodHandles$Lookup 是 MethodHandles 类的一个公共静态最终(public static final)内部类。// #66, #65, 和 #69 是常量池中的索引,分别代表 MethodHandles$Lookup 类、MethodHandles$Lookup 类型的引用和 MethodHandles 类。
这种表示方法用于在类文件的常量池中引用类和接口。public static final #66= #65 of #69; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles// 这部分描述了用于支持动态语言特性(如lambda表达式)的引导方法(Bootstrap Methods)
BootstrapMethods:// 这是一个引导方法的条目,用于lambda表达式的动态调用。// #31 是常量池中的索引,指向 LambdaMetafactory.metafactory 方法的引用。// invokestatic 表示这是一个静态方法调用。// LambdaMetafactory.metafactory 是一个引导方法,用于在运行时动态创建lambda表达式的实例。0: #31 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;// 这些是传递给 LambdaMetafactory.metafactory 方法的参数。Method arguments:#32 (Ljava/lang/Object;)V // 这是一个方法类型的描述,表示一个接受 Object 参数并返回 void 的方法。#33 invokestatic LambdaExample.lambda$main$0:(Ljava/lang/String;)V // 这是对 LambdaExample.lambda$main$0 方法的引用,这个方法是由lambda表达式生成的静态方法,接受一个 String 参数并返回 void。#34 (Ljava/lang/String;)V // 这是另一个方法类型的描述,与上面的描述相同,但参数类型为 String。

2.5.3 invokedynamic指令的作用:

invokedynamic 指令是Java 7中引入的一项字节码指令,它为Java虚拟机(JVM)提供了动态方法调用的能力。这项特性主要是为了提高Java平台对动态类型语言的支持,但它也为Java语言本身带来了新的可能性,特别是在实现lambda表达式和方法引用等Java 8特性时。

  1. 动态类型语言支持

    • invokedynamic之前,JVM主要针对静态类型语言设计。invokedynamic提供了一种机制,允许JVM在运行时动态解析方法调用,这对于动态类型语言(如Groovy、JRuby等)非常有用。
  2. 性能优化

    • 传统的方法调用(如invokevirtualinvokeinterface等)在编译时就确定了目标方法的调用,而invokedynamic允许在运行时解析目标方法。这种延迟绑定的方式使得JVM可以在运行时根据实际情况进行优化,提高性能。
  3. Lambda表达式和方法引用

    • Java 8引入了lambda表达式和方法引用,这些特性背后就是通过invokedynamic实现的。invokedynamic使得JVM可以在运行时动态创建和调用这些函数式接口的实现,而不需要生成大量的匿名类,从而减少了内存的占用并提高了性能。
  4. 更灵活的编程模型

    • invokedynamic提供的动态方法调用机制,为Java开发者带来了更多的灵活性。它允许开发者在运行时根据需要动态改变方法的行为,这在某些高级编程模式中非常有用。

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

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

相关文章

基于springboot的数据库原理教学案例案例库管理系统

目录 毕设制作流程功能和技术介绍系统实现截图开发核心技术介绍&#xff1a;使用说明开发步骤编译运行代码执行流程核心代码部分展示可行性分析软件测试详细视频演示源码获取 毕设制作流程 &#xff08;1&#xff09;与指导老师确定系统主要功能&#xff1b; &#xff08;2&am…

PMP--三模--解题--71-80

文章目录 7.成本管理--S曲线--S曲线对累计值进行监督和报告--S曲线可以同时报告成本与进度情况。适用于预测和敏捷项目。14.敏捷--信息发射源--是一种可见的实物展示其向组织内其他成员提供信息在不干扰团队的情况下即时实现知识共享。71、 [单选] 项目经理正在为刚刚进入第三次…

windows配置C++编译环境和VScode C++配置(保姆级教程)

1.安装MinGW-w64 MinGW-w64是一个开源的编译器套件,适用于Windows平台,支持32位和64位应用程序的开发。它包含了GCC编译器、GDB调试器以及其他必要的工具,是C++开发者在Windows环境下进行开发的重要工具。 我找到了一个下载比较快的链接:https://gitcode.com/open-source-…

FastAPI 第九课 -- 表单数据

目录 一. 前言 二. 声明表单数据模型 三. 在路由中接收表单数据 四. 表单数据的验证和文档生成 五. 处理文件上传 一. 前言 在 FastAPI 中&#xff0c;接收表单数据是一种常见的操作&#xff0c;通常用于处理用户通过 HTML 表单提交的数据。 FastAPI 提供了 Form 类型&a…

C++发邮件:如何轻松实现邮件自动化发送?

C发邮件的详细步骤与教程指南&#xff1f;如何在C中发邮件&#xff1f; 无论是定期发送报告、通知客户还是管理内部沟通&#xff0c;自动化邮件系统都能显著提升工作效率。AokSend将详细介绍如何使用C发邮件&#xff0c;实现邮件自动化发送&#xff0c;帮助您轻松管理邮件通信…

车视界系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;汽车品牌管理&#xff0c;汽车颜色管理&#xff0c;用户管理&#xff0c;汽车信息管理&#xff0c;汽车订单管理系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;汽车信息&#xff0c;我…

4.1、FineReport单元格扩展和父子格

单元格扩展 1、配置数据集 2、纵向扩展 方法一&#xff1a; 方法二&#xff1a; 结果 多个字段纵向 2、横向扩展 方法一&#xff1a; 方法二&#xff1a; 结果 父子格 没什么特殊要求&#xff0c;就保持默认 1、右边的值默认以左边为左父格 2、下边的值默认以上边…

【Windows】如何取消显示Windows聚焦在桌面上生成的“了解此图片”图标

如下图所示&#xff0c;在更换Windows聚焦显示的时候&#xff0c;会在桌面多出一个“了解此图片”的图标&#xff0c;看着很烦&#xff0c;但又因为Windows聚焦自带的壁纸比其他主题的壁纸好看很多。 下面是消除办法&#xff1a; 打开注册表&#xff08;按WindowsR&#xff0…

【COSMO-SkyMed系列的4颗卫星主要用途】

COSMO-SkyMed系列的4颗卫星主要用于提供一个多用途的对地观测平台&#xff0c;服务于民间、公共机构、军事和商业领域。以下是这4颗卫星的主要用途&#xff1a; 民防与环境风险管理&#xff1a; 卫星的高分辨率雷达图像可用于监测自然灾害&#xff0c;如地震、洪水、滑坡等&am…

51单片机学习第六课---B站UP主江协科技

DS18B20 1、基本知识讲解 2、DS18B20读取温度值 main.c #include<regx52.h> #include"delay.h" #include"LCD1602.h" #include"key.h" #include"DS18B20.h"float T; void main () {LCD_Init();LCD_ShowString(1,1,"temp…

汽车革命下半场AI先锋:广汽为新“智”汽车装配大模型“底盘”

汽车革命的上半场是电动化&#xff0c;下半场是智能化&#xff0c;这是全球汽车产业普遍认同的观点。当前&#xff0c;我国汽车产业已经在电动化上半场取得了显著成效&#xff0c;在下半场智能化的全球战场能否胜出&#xff0c;关键看车企的创新意愿和研发实力。 在2024年9月1…

【Orange Pi 5嵌入式应用编程】-用户空间GPIO控制

用户空间GPIO控制 文章目录 用户空间GPIO控制1、嵌入式Linux的GPIO子系统介绍1.1 sysfs文件访问GPIO1.2 通过字符设备访问GPIO1.3 库与工具2、RK3588的GPIO介绍3、用户空间操作GPIO编程3.1 硬件准备3.2 通过libgpio操作GPIO3.2.1 GPIO输出3.2.3 GPIO输入3.2.3 边沿事件检测(中断…

《Windows PE》3.2.3 NT头-扩展头

■扩展头&#xff08;可选标头仅限映像文件&#xff09; OptionalHeader字段描述了可执行文件的更多细节和布局信息&#xff0c;如图像基址、入口点、数据目录、节表等。它的具体结构取决于文件的机器架构&#xff0c;可以是IMAGE_OPTIONAL_HEADER32&#xff08;32位&#xff…

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

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

html空单元格的占位

先上代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title></head><body> <H1>我的WEB页面</H1><table border"2px" bgcolor"#7fffd4&…

【Ubuntu】安装常用软件包-mysql

我的几个服务是部署在docker的同一个网络里&#xff0c;这样相互访问就可以通过docker容器的名字访问&#xff0c;比如容器A访问容器B&#xff0c;就可以http://B:8080/xxx 这样访问&#xff0c;不用关心ip是多少。 所以mysql前面文章给安装到主机里&#xff0c;感觉有点坑自己…

JavaScript 网页设计案例 简单的电商案例 页面切换 数据搜索 动态网页

JavaScript 网页设计案例 简单的电商案例 页面切换 数据搜索 动态网页 1. 案例描述 以下是一个简单的产品展示网页&#xff0c;用户可以通过点击不同的产品类别按钮来查看相应的产品&#xff0c;且在鼠标悬停时显示产品详情。页面还将包含一个搜索框&#xff0c;用户可以输入…

深蕾半导体Astra™ SL1620详细介绍,嵌入式物联网处理器

一&#xff0c;SL1620是什么 Astra™ SL系列是深蕾半导体推出的高度集成的嵌入式物联网处理器SoC&#xff08;System on Chip&#xff09;系列产品&#xff0c;专为多模式消费者、企业和工业物联网工作负载而设计。SL1620是Astra™ SL系列中的一款成本和功耗优化的安全嵌入式So…

解决 Failed to connect to 127.0.0.1 port XXXX: Connection refused问题

查看自己的代理&#xff0c;如果有设置&#xff0c;取消即可。注意https还是http&#xff0c;或者都取消算了 git config --global http.proxy git config --global --unset http.proxygit config --global https.proxy git config --global --unset https.proxy注意如果有人在…

进程的管道

进程之间的通信有两种&#xff0c;无名管道通信和有名管道通信&#xff0c; 为什么有通信呢&#xff0c;可以理解为你有一个同事&#xff0c;你两干一件事从不同的方向&#xff0c;哪一件事你干&#xff0c;哪一件事他干&#xff0c;你俩得知道吧&#xff0c;差不多是这个意思…