从Lambda底层实现一窥invokedynamic魅力
Java 8引入Lambda表达式替代匿名内部类,底层通过invokedynamic指令动态生成实现类。Lambda捕获变量时生成带参构造方法,无状态Lambda优化为单例。序列化通过SerializedLambda实现,反序列化时重建Lambda实例。性能测试显示Lambda调用效率接近原生代码。应用场景包括动态Getter/Setter、ORM框架方法引用解析等。invokedynamic还支撑了字符串拼接优化、switch模式匹配等特性。
文章目录
从匿名内部类到Lambda表达式的演进
自Java 8发布以来,Lambda表达式以其简洁的语法,迅速取代了匿名内部类在大多数场景下的应用。一个典型的例子便是创建线程:
// JDK8之前Runnable r = new Runnable() { @Override public void run() { System.out.println("Thread running..."); }};new Thread(r).start();
// JDK8及之后Runnable r = () -> System.out.println("Thread running...");new Thread(r).start();代码量的锐减和可读性的提升,曾让笔者自然地认为,Lambda表达式或许只是Java为实现函数式接口而提供的一种“语法糖”,其底层本质仍然是我们熟悉的匿名内部类。
然而,当我们尝试编译包含Lambda表达式的类MyClass.java后,在编译产物目录中,我们却没有找到预想中的MyClass$1.class这样的匿名内部类文件。这一发现打破了我们的猜想,并引出一个核心问题:
如果不是匿名内部类,JVM究竟是如何实现Lambda的?
invokedynamic——动态调用的基石
为了探寻真相,我们可以深入字节码层面,看看JDK把lambda表达式编译成了什么。以下所有示例均基于Eclipse Temurin JDK 24.0.2+12。
通过javap -v -p MyClass.class命令,我们得到了以下输出(截取部分):
{ public study.invokedynamic.MyClass(); descriptor: ()V flags: (0x0001) 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 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lstudy/invokedynamic/MyClass;
public static void main(); descriptor: ()V flags: (0x0009) ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=0 0: invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 5: astore_0 6: new #11 // class java/lang/Thread 9: dup 10: aload_0 11: invokespecial #13 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 14: invokevirtual #16 // Method java/lang/Thread.start:()V 17: return LineNumberTable: line 8: 0 line 9: 6 line 10: 17 LocalVariableTable: Start Length Slot Name Signature 6 12 0 r Ljava/lang/Runnable;
private static void lambda$main$0(); descriptor: ()V flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC Code: stack=2, locals=0, args_size=0 0: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #25 // String Thread running... 5: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0}SourceFile: "MyClass.java"BootstrapMethods: 0: #51 REF_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: #47 ()V #48 REF_invokeStatic study/invokedynamic/MyClass.lambda$main$0:()V #47 ()V通过上面的字节码,我们可以发现一些端倪:
- main方法中没有看到直接创建Runnable实例的逻辑,而是出现了一个陌生的指令:
invokedynamic - Lambda方法体中打印
Thread running...的逻辑被放在了一个lambda$main$0方法中 - 有一个特殊的
BootstrapMethods引用了lambda$main$0方法
首先介绍一下invokedynamic(下文简称indy)指令的背景:Java虚拟机规范中定义了五种方法调用字节码指令,其中invokestatic、invokespecial、invokevirtual、invokeinterface这四种指令的调用目标在编译期就已经确定。而从JDK7开始才加入的indy则截然不同,它是一种 运行时动态绑定 的指令。当JVM首次执行到indy指令时,它不会直接去调用目标方法,而是会先调用一个被称为 “引导方法(Bootstrap Method)” 的特殊方法。此方法的返回值是一个CallSite(调用点)对象,该对象才真正链接到我们最终要执行的目标方法。后续再执行到此indy指令时,便会直接使用这个已绑定的CallSite,无需再次“引导”。
不了解相关概念的读者可能会被这段话绕晕,我们先简单介绍indy工作流中涉及的几个核心概念:
- 方法句柄(Method Handle):
java.lang.invoke.MethodHandle,可以理解为对一个方法(构造器、静态或实例方法)的直接、可执行的引用,类似于C语言中的函数指针。它是indy机制的核心承载者。 - 调用点(Call Site):
java.lang.invoke.CallSite,可以看作是方法句柄的容器,它负责将一个indy指令与一个具体的方法句柄进行“链接”。 - 引导方法(Bootstrap Method,下称BSM): 可以理解为一个特殊的静态方法,在
invokedynamic指令首次执行时被调用,它的职责是创建并返回一个调用点(CallSite)对象,用于将该指令动态链接到具体的目标方法上。
那么在深入细节之前,我们先抛出一个疑问:为什么Java团队要舍弃成熟的匿名内部类方案,转而使用复杂的indy机制来实现Lambda表达式呢?
带着疑问,我们来一步步揭开Lambda的底层实现。
循序渐进,一步步揭开Lambda的底层实现
从无状态lambda开始
通过观察上文示例的字节码,我们可以看到indy指令指向的BSM是java.lang.invoke.LambdaMetafactory#metafactory。阅读该方法的注释,我们得知它的功能是:“通过委托给一个提供的方法句柄,并在进行适当的类型适配和参数校验后,实现一个或多个接口。通常用作invokedynamic调用点的引导方法,以支持Java编程语言中的lambda表达式和方法引用表达式特性。”。
现在,我们先从不捕获外部变量的Lambda例子看起:
public class LambdaTest1 { public static void main() { // 例子1: 常规Lambda Runnable r1 = () -> System.out.println("Lambda 1"); r1.run();
// 例子2: 方法引用 Runnable r2 = LambdaTest1::printMessage; r2.run(); }
private static void printMessage() { System.out.println("Lambda 2"); }}通过字节码来分析Lambda表达式的运行过程未免有点过于抽象,我们不妨换种思路:
目前我们已经得知Lambda表达式是通过indy指令实现的,而indy指令首次执行会调用对应BSM,对应上文的分析这个BSM为java.lang.invoke.LambdaMetafactory#metafactory方法,那么我们可以通过断点调试来分析:

可以看到,metafactory方法定义了6个参数,其中前三个是BSM规范中的固定参数:
MethodHandles.Lookup caller:代表调用者上下文信息和访问权限的Lookup对象String interfaceMethodName:indy指令要动态调用的方法名字符串。对于Lambda表达式,这个名字就是它所实现的函数式接口的抽象方法名(例如,Runnable的run,Function的apply)。MethodType factoryType:代表indy指令要动态调用的方法签名。对于Lambda表达式,这个签名通常是生成Lambda实例的工厂方法的类型(例如,返回一个Runnable,不接收任何参数)。
而metafactory方法的后三个参数,则是为了实现Lambda表达式而需要的额外参数:
MethodType interfaceMethodType:描述函数式接口中抽象方法的方法签名。需要注意的是对于泛型参数需要擦除类型(例如,对于Runnable的方法签名为无参、无返回:()void,对于Function<String, Integer>则需要擦除泛型:(Object)Object)MethodHandle implementation:指向Lambda体代码实现或方法引用的方法句柄。对于我们的例子1,它指向了一个静态私有方法:lambda$main$0,这符合我们前面在字节码中观察到的现象;而对于例子2,它直接指向了LambdaTest1#printMessage方法。这里就能看出Lambda与方法引用的区别,对于Lambda,编译器需要将其代码提取到一个方法中,以供方法句柄引用。MethodType dynamicMethodType:描述了调用Lambda实例时 实际的方法签名。它与第四个参数interfaceMethodType的区别在于它需要匹配实际调用的方法签名,所以这里不需要擦除泛型,对于Function<String, Integer>是:(String)Integer
了解了这些参数,metafactory的逻辑就不难看懂了:
首先创建了一个InnerClassLambdaMetafactory实例,从名字不难看出JDK最终还是通过内部类来实现Lambda表达式。
然后调用validateMetafactoryArgs方法校验上述的参数,包括参数数量是否匹配、类型是否匹配(支持自动装箱拆箱),返回值是否匹配(无返回值的函数式接口可以适配带返回值的方法实现)。
最后通过buildCallSite方法来构建调用点,Lambda的主要实现逻辑就在这个方法中:
@OverrideCallSite buildCallSite() throws LambdaConversionException { // 获取Lambda实现类 final Class<?> innerClass = spinInnerClass(); // disableEagerInitialization默认为false,这个开关的作用是控制无状态Lambda是否懒加载生成单例对象 if (factoryType.parameterCount() == 0 && disableEagerInitialization) { try { return new ConstantCallSite(caller.findStaticGetter(innerClass, LAMBDA_INSTANCE_FIELD, factoryType.returnType())); } catch (ReflectiveOperationException e) { throw new LambdaConversionException( "Exception finding " + LAMBDA_INSTANCE_FIELD + " static field", e); } } else { try { // 获取Lambda实现类的构造方法,构造方法的参数数量代表了捕获外部变量的数量 MethodHandle mh = caller.findConstructor(innerClass, constructorType); if (factoryType.parameterCount() == 0) { // 如果Lambda没有捕获外部变量,则使用该构造方法创建一个Lambda实例,并构建一个调用点,使其固定返回这个实例 Object inst = mh.invokeBasic(); return new ConstantCallSite(MethodHandles.constant(interfaceClass, inst)); } else { // 如果捕获了外部变量,则只能每次调用都创建一个Lambda实例 return new ConstantCallSite(mh.asType(factoryType)); } } catch (ReflectiveOperationException e) { throw new LambdaConversionException("Exception finding constructor", e); } catch (Throwable e) { throw new LambdaConversionException("Exception instantiating lambda object", e); } }}现在,我们可以部分回答之前的问题了:为什么不直接用匿名内部类? 因为对于无状态Lambda,每次调用匿名内部类都会new一个新对象,而indy+LambdaMetafactory机制可以实现单例复用,极大地提升了性能,减少了不必要的GC压力。
继续往下看,查看spinInnerClass方法是如何创建Lambda实现类的:
private Class<?> spinInnerClass() throws LambdaConversionException { // CDS does not handle disableEagerInitialization or useImplMethodHandle if (!disableEagerInitialization && !useImplMethodHandle) { // 可以看到,这里是利用CDS机制缓存了生成的类,以便于其他Java进程使用 if (CDS.isUsingArchive()) { // load from CDS archive if present Class<?> innerClass = LambdaProxyClassArchive.find(targetClass, interfaceMethodName, factoryType, interfaceMethodType, implementation, dynamicMethodType, isSerializable, altInterfaces, altMethods); if (innerClass != null) return innerClass; }
// include lambda proxy class in CDS archive at dump time if (CDS.isDumpingArchive()) { Class<?> innerClass = generateInnerClass(); LambdaProxyClassArchive.register(targetClass, interfaceMethodName, factoryType, interfaceMethodType, implementation, dynamicMethodType, isSerializable, altInterfaces, altMethods, innerClass); return innerClass; }
} // 核心的机制都在下面这个方法内 return generateInnerClass();}private Class<?> generateInnerClass() throws LambdaConversionException { List<ClassDesc> interfaces; ClassDesc interfaceDesc = classDesc(interfaceClass); // 检查是否实现Serializable接口 boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(interfaceClass); if (altInterfaces.length == 0) { interfaces = List.of(interfaceDesc); } else { // 由于一个接口也可能继承多个其他接口,这些接口都需要被Lambda实现类实现 Set<ClassDesc> itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); itfs.add(interfaceDesc); for (Class<?> i : altInterfaces) { itfs.add(classDesc(i)); accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(i); } interfaces = List.copyOf(itfs); } final boolean finalAccidentallySerializable = accidentallySerializable; // 可以看到,JDK24的实现中,使用了最新的ClassFile API来生成Lambda实现类 final byte[] classBytes = ClassFile.of().build(lambdaClassEntry, pool, new Consumer<ClassBuilder>() { @Override public void accept(ClassBuilder clb) { clb.withFlags(ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC) .withInterfaceSymbols(interfaces); // 如果Lambda捕获了外部变量,则需要通过构造方法传入 for (int i = 0; i < argDescs.length; i++) { clb.withField(argName(i), argDescs[i], ACC_PRIVATE | ACC_FINAL); }
generateConstructor(clb);
if (factoryType.parameterCount() == 0 && disableEagerInitialization) { generateClassInitializer(clb); }
// 实现函数式接口声明的抽象方法 clb.withMethodBody(interfaceMethodName, methodDesc(interfaceMethodType), ACC_PUBLIC, forwardingMethod(interfaceMethodType));
// 如有必要,生成额外的桥接方法,用于协变返回值类型和类型擦除 if (altMethods != null) { for (MethodType mt : altMethods) { clb.withMethodBody(interfaceMethodName, methodDesc(mt), ACC_PUBLIC | ACC_BRIDGE, forwardingMethod(mt)); } }
// 如果实现了Serializable接口,则额外生成writeReplace方法 if (isSerializable) generateSerializationFriendlyMethods(clb); else if (finalAccidentallySerializable) generateSerializationHostileMethods(clb); } });
// 生成字节码后,在JVM中定义该实现类 try { // this class is linked at the indy callsite; so define a hidden nestmate var classdata = useImplMethodHandle? implementation : null; // 这里有一个lambdaProxyClassFileDumper,支持通过VM参数开启,将Lambda实现类的字节码dump出来 return caller.makeHiddenClassDefiner(lambdaClassName, classBytes, lambdaProxyClassFileDumper, NESTMATE_CLASS | STRONG_LOADER_LINK) .defineClass(!disableEagerInitialization, classdata); } catch (Throwable t) { throw new InternalError(t); }}可以看到,Lambda实现类的生成还是比较复杂的,ClassFile API也是较为底层的API,不过在JVM中定义实现类时,传入了一个lambdaProxyClassFileDumper,其定义如下:
// To dump the lambda proxy classes, set this system property:// -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles// or -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles=truefinal String dumpProxyClassesKey = "jdk.invoke.LambdaMetafactory.dumpProxyClassFiles";lambdaProxyClassFileDumper = ClassFileDumper.getInstance(dumpProxyClassesKey, "DUMP_LAMBDA_PROXY_CLASS_FILES");注释也提到,支持通过添加-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles这一VM参数,来转储Lambda实现类的字节码。我们添加这一参数,然后重新运行程序,在项目根目录下的DUMP_LAMBDA_PROXY_CLASS_FILES文件夹中找到了以下类,通过IDEA反编译后的结果如下:
// 例子1生成的Lambda实现类:LambdaTest1$$Lambda.0x0000021238003548.class// $FF: synthetic classfinal class LambdaTest1$$Lambda implements Runnable { private LambdaTest1$$Lambda() { }
public void run() { LambdaTest1.lambda$main$0(); }}// 例子2生成的Lambda实现类:LambdaTest1$$Lambda.0x0000021238003778.class// $FF: synthetic classfinal class LambdaTest1$$Lambda implements Runnable { private LambdaTest1$$Lambda() { }
public void run() { LambdaTest1.printMessage(); }}可以看到,程序在运行期动态生成了两个随机名称的内部类,分别对应我们代码中的两个例子。至此,我们大致了解了Lambda在底层是怎么实现的。
登堂入室,有状态Lambda以及方法签名适配
接下来,我们再来看一些更复杂的场景:
public class LambdaTest2 { private static int staticValue = 0; private int instanceValue = 100;
public static void main() { // 例子3: 捕获静态变量的Lambda IntUnaryOperator addValue = (x) -> x + staticValue; System.out.println("Result 3: " + addValue.applyAsInt(1)); //1 staticValue = 1; System.out.println("Result 3: " + addValue.applyAsInt(1)); // 2
// 例子4: 捕获实例变量的Lambda LambdaTest2 obj = new LambdaTest2(); IntUnaryOperator addInstanceValue = (x) -> x + obj.instanceValue; System.out.println("Result 4: " + addInstanceValue.applyAsInt(1)); // 101 obj.instanceValue = 101; System.out.println("Result 4: " + addInstanceValue.applyAsInt(1)); // 102
// 例子5: 捕获局部变量的Lambda int localValue = 5; IntUnaryOperator addLocalValue = (x) -> x + localValue; System.out.println("Result 5: " + addLocalValue.applyAsInt(1)); // 6 // localValue = 6; // 由于Java的限制,被Lambda表达式捕获的局部变量必须是final或effectively final
// 例子6: 实例方法引用 obj.instanceValue = 99; IntUnaryOperator addInstanceMethod = obj::addToInstanceValue; System.out.println("Result 6: " + addInstanceMethod.applyAsInt(1)); // 100 obj.instanceValue = 100; System.out.println("Result 6: " + addInstanceMethod.applyAsInt(1)); // 101
// 例子7: 参数与返回值类型适配 Consumer<Integer> anotherAdd = LambdaTest2::addToStaticValue; anotherAdd.accept(1); System.out.println("Result 7: " + staticValue); // 2 }
private static int addToStaticValue(int x) { return staticValue += x; }
private int addToInstanceValue(int x) { return instanceValue += x; }}通过反编译,我们看到编译器在LambdaTest2这个类中额外生成了以下几个私有静态方法:
private static int lambda$main$0(int var0) { return var0 + LambdaTest2.staticValue;}
private static int lambda$main$1(LambdaTest2 var0, int var1) { return var1 + var0.instanceValue;}
private static int lambda$main$2(int var0, int var1) { return var0 + var1;}接下来,添加-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles参数,运行一次程序,我们从DUMP_LAMBDA_PROXY_CLASS_FILES文件夹中找到了以下类:
// $FF: synthetic classfinal class LambdaTest2$$Lambda implements IntUnaryOperator { private LambdaTest2$$Lambda() { }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$0(var1); }}// $FF: synthetic classfinal class LambdaTest2$$Lambda implements IntUnaryOperator { private final LambdaTest2 arg$1;
// 通过构造方法传入LambdaTest2实例,以便下面获取该类的实例变量 private LambdaTest2$$Lambda(LambdaTest2 var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$1(this.arg$1, var1); }}// $FF: synthetic classfinal class LambdaTest2$$Lambda implements IntUnaryOperator { private final int arg$1;
// 通过构造方法传入局部变量 private LambdaTest2$$Lambda(int var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$2(this.arg$1, var1); }}// $FF: synthetic classfinal class LambdaTest2$$Lambda implements IntUnaryOperator { private final LambdaTest2 arg$1;
private LambdaTest2$$Lambda(LambdaTest2 var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return this.arg$1.addToInstanceValue(var1); }}// $FF: synthetic classfinal class LambdaTest2$$Lambda implements Consumer { private LambdaTest2$$Lambda() { }
public void accept(Object var1) { LambdaTest2.addToStaticValue(((Integer)var1).intValue()); // 自动进行装箱拆箱 // 忽略了addToStaticValue的返回值,以适配Consumer接口 }}所以,最终等价于以下代码:
public class LambdaTest2 { private static int staticValue = 0; private int instanceValue = 100;
public static void main() { // 例子3: 捕获静态变量的Lambda IntUnaryOperator addValue = Lambda_0.LAMBDA_INSTANCE$; System.out.println("Result 3: " + addValue.applyAsInt(1)); staticValue = 1; System.out.println("Result 3: " + addValue.applyAsInt(1));
// 例子4: 捕获实例变量的Lambda LambdaTest2 obj = new LambdaTest2(); IntUnaryOperator addInstanceValue = new Lambda_1(obj); System.out.println("Result 4: " + addInstanceValue.applyAsInt(1)); obj.instanceValue = 101; System.out.println("Result 4: " + addInstanceValue.applyAsInt(1));
// 例子5: 捕获局部变量的Lambda int localValue = 5; IntUnaryOperator addLocalValue = new Lambda_2(localValue); System.out.println("Result 5: " + addLocalValue.applyAsInt(1)); // localValue = 6; // 由于Java的限制,被Lambda表达式捕获的局部变量必须是final或effectively final
// 例子6: 实例方法引用 IntUnaryOperator addInstanceMethod = new Lambda_3(obj); System.out.println("Result 6: " + addInstanceMethod.applyAsInt(1)); obj.instanceValue = 100; System.out.println("Result 6: " + addInstanceMethod.applyAsInt(1));
// 例子7: 参数与返回值类型适配 Consumer<Integer> anotherAdd = Lambda_4.LAMBDA_INSTANCE$; anotherAdd.accept(1); System.out.println("Result 6: " + staticValue); }
private static int addToStaticValue(int x) { return staticValue += x; }
private int addToInstanceValue(int x) { return instanceValue += x; }
// 例子3: 捕获静态变量的Lambda private static int lambda$main$0(int var0) { return var0 + LambdaTest2.staticValue; }
final static class Lambda_0 implements IntUnaryOperator { private static final Lambda_0 LAMBDA_INSTANCE$ = new Lambda_0();
private Lambda_0() { }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$0(var1); } }
// 例子4: 捕获实例变量的Lambda private static int lambda$main$1(LambdaTest2 var0, int var1) { return var1 + var0.instanceValue; }
final static class Lambda_1 implements IntUnaryOperator { private final LambdaTest2 arg$1;
private Lambda_1(LambdaTest2 var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$1(this.arg$1, var1); } }
// 例子5: 捕获局部变量的Lambda private static int lambda$main$2(int var0, int var1) { return var0 + var1; }
final static class Lambda_2 implements IntUnaryOperator { private final int arg$1;
private Lambda_2(int var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return LambdaTest2.lambda$main$2(this.arg$1, var1); } }
// 例子6: 实例方法引用 final static class Lambda_3 implements IntUnaryOperator { private final LambdaTest2 arg$1;
private Lambda_3(LambdaTest2 var1) { this.arg$1 = var1; }
public int applyAsInt(int var1) { return this.arg$1.addToInstanceValue(var1); } }
// 例子7: 参数与返回值类型适配 final static class Lambda_4 implements Consumer { private static final Lambda_4 LAMBDA_INSTANCE$ = new Lambda_4();
private Lambda_4() { }
public void accept(Object var1) { LambdaTest2.addToStaticValue(((Integer) var1).intValue()); } }}至此,我们一步步揭开了Lambda的神秘面纱,并且从中了解到Lambda的几个特点:
- 无捕获或者捕获类静态变量的Lambda会被优化为单例模式,避免每次创建Lambda实例
- Lambda捕获的局部变量必须是final声明的或者事实上final(没有被重新赋值),笔者猜测是故意设置的这种限制,避免程序员使用不当造成困扰。
- Lambda会自动对参数及返回值进行装箱拆箱,并且无返回值的接口方法可以自动适配带返回值的方法实现。
SerializedLambda——安全地实现Lambda序列化
前面我们分析InnerClassLambdaMetafactory#generateInnerClass方法生成Lambda实现类时,发现了如果函数式接口实现了Serializable接口,则通过generateSerializationFriendlyMethods额外生成序列化友好的方法。由此可见,Lambda表达式也是支持序列化的。但是Lambda表达式说到底只是一个承载了一些代码逻辑的方法,JDK是如何实现把代码逻辑序列化到字节数组中,同时还能保证安全的呢?不妨通过一个例子继续分析:
public class LambdaTest3 { interface SFunction<T, R> extends Function<T, R>, Serializable { }
public static void main() { // 例子8:方法引用 SFunction<String, Integer> sFunc = String::length; System.out.println(sFunc.apply("hello")); // 5
// 例子9:Lambda体 SFunction<String, Integer> sFunc2 = str -> -str.length(); System.out.println(sFunc2.apply("hello")); // -5
// 将例子8的Lambda序列化 var baos = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(baos); objectOutputStream.writeObject(sFunc); objectOutputStream.close();
// 再反序列化回来 byte[] serializedLambda = baos.toByteArray(); ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(serializedLambda)); SFunction<String, Integer> deserializedFunc = (SFunction<String, Integer>) objectInputStream.readObject(); objectInputStream.close(); System.out.println(deserializedFunc.apply("hello")); // 5 }}同样添加-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles参数,运行一次程序,我们看看生成的Lambda实现类:
import java.lang.invoke.SerializedLambda;
// $FF: synthetic classfinal class LambdaTest3$$Lambda implements LambdaTest3.SFunction { private LambdaTest3$$Lambda() { }
public Object apply(Object var1) { return ((String)var1).length(); }
private final Object writeReplace() { return new SerializedLambda(LambdaTest3.class, "study/invokedynamic/LambdaTest3$SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 5, "java/lang/String", "length", "()I", "(Ljava/lang/String;)Ljava/lang/Integer;", new Object[0]); }}import java.lang.invoke.SerializedLambda;
// $FF: synthetic classfinal class LambdaTest3$$Lambda implements LambdaTest3.SFunction { private LambdaTest3$$Lambda() { }
public Object apply(Object var1) { return LambdaTest3.lambda$main$699ec26b$1((String)var1); }
private final Object writeReplace() { return new SerializedLambda(LambdaTest3.class, "study/invokedynamic/LambdaTest3$SFunction", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", 6, "study/invokedynamic/LambdaTest3", "lambda$main$699ec26b$1", "(Ljava/lang/String;)Ljava/lang/Integer;", "(Ljava/lang/String;)Ljava/lang/Integer;", new Object[0]); }}可以看到,Lambda实现类为序列化额外生成了writeReplace方法。根据Java序列化规范,如果一个类定义了Object writeReplace()方法,那么在序列化该类的实例时,实际被序列化的是writeReplace方法的返回值。
所以Lambda实例在序列化时会被该方法返回的SerializedLambda对象代替,而这个对象中储存了可用于重建这个Lambda一些元信息,包括Lambda表达式所在类、实现的函数式接口名称、实现方法所在类,实现方法的名称等。
而SerializedLambda类中定义了一个readResolve方法。根据规范,如果一个类定义了Object readResolve() throws ObjectStreamException方法,那么在ObjectInputStream从输入流中读取并创建了一个该类的实例之后,在返回给调用者之前,JVM会自动检查并调用这个readResolve方法。最终返回给调用者的,将是readResolve方法的返回值,而不是最初创建的那个对象。
SerializedLambda的readResolve方法逻辑如下:
private Object readResolve() throws ObjectStreamException { try { Method deserialize = capturingClass.getDeclaredMethod("$deserializeLambda$", SerializedLambda.class); deserialize.setAccessible(true); return deserialize.invoke(null, this); } catch (ReflectiveOperationException roe) { throw new InvalidObjectException("ReflectiveOperationException during deserialization", roe); }}再看看编译器为LambdaTest3这个类生成了哪些额外的方法:
private static Integer lambda$main$699ec26b$1(String str) { return -str.length();}
private static Object $deserializeLambda$(SerializedLambda lambda) { switch (lambda.getImplMethodName()) { case "length": { if (lambda.getImplMethodKind() != 5 || !lambda.getFunctionalInterfaceClass().equals("study/invokedynamic/LambdaTest3$SFunction") || !lambda.getFunctionalInterfaceMethodName().equals("apply") || !lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") || !lambda.getImplClass().equals("java/lang/String") || !lambda.getImplMethodSignature().equals("()I") ) break; return (SFunction<String, Integer>) String::length; } case "lambda$main$699ec26b$1": { if (lambda.getImplMethodKind() != 6 || !lambda.getFunctionalInterfaceClass().equals("study/invokedynamic/LambdaTest3$SFunction") || !lambda.getFunctionalInterfaceMethodName().equals("apply") || !lambda.getFunctionalInterfaceMethodSignature().equals("(Ljava/lang/Object;)Ljava/lang/Object;") || !lambda.getImplClass().equals("study/invokedynamic/LambdaTest3") || !lambda.getImplMethodSignature().equals("(Ljava/lang/String;)Ljava/lang/Integer;") ) break; return (SFunction<String, Integer>) str -> -str.length(); } } throw new java.lang.IllegalArgumentException("Invalid lambda deserialization");}这下就能对应得上了,Lambda的序列化/反序列化流程可以归纳为:
实际应用场景
那么,深入了解了Lambda表达式的底层实现后,我们可以在哪些场景下利用这些特性呢?
小试牛刀,实现逼近原生性能的动态Getter/Setter
LambdaMetafactory 除了用于实现Lambda表达式之外,本身也为我们提供了一个强大的武器:在运行时动态地调用任意对象中声明的方法(受JVM权限校验限制),并且其性能可以逼近原生代码调用。
在一些需要动态操作对象属性的场景中,我们通常会想到使用Java的反射API。然而,反射的性能开销是众所周知的,因为它涉及权限检查、方法解析等一系列耗时操作;虽然方法句柄的出现提供了一种比反射更高效的选择,但想要让方法句柄的调用被JIT优化为原生调用,最关键的前提是方法句柄实例需要被声明为 static final。这使得它在需要为非预知的字段创建访问器的场景下显得力不从心。
而LambdaMetafactory 恰好能完美地解决这个矛盾。我们可以在运行时,为某个类的特定字段的Getter/Setter方法,手动调用 LambdaMetafactory.metafactory 来动态生成一个实现了标准函数式接口(如 Getter对应Function、Setter对应BiConsumer)的Lambda实例。由于Lambda基于内部类实现的特点,调用这个Lambda实例,其性能几乎与直接编写的 object.getField() 或 object.setField(...) 代码无异。
当然,口说无凭,我们跑上几轮Benchmark来证明。以下是Benchmark代码:
点击展开Benchmark代码
import org.openjdk.jmh.annotations.*;import org.openjdk.jmh.runner.Runner;import org.openjdk.jmh.runner.RunnerException;import org.openjdk.jmh.runner.options.Options;import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.lang.invoke.*;import java.lang.reflect.Method;import java.util.function.BiConsumer;import java.util.function.Function;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
/** * @author brooke_zb */@SuppressWarnings("unchecked")@State(Scope.Thread)@BenchmarkMode(Mode.Throughput)@Warmup(iterations = 100, time = 100, timeUnit = MILLISECONDS)@Measurement(iterations = 100, time = 100, timeUnit = MILLISECONDS)@Threads(8)@OutputTimeUnit(MILLISECONDS)public class LambdaBenchmark { public static class Point { private Double x; private Double y;
public Point(Double x, Double y) { this.x = x; this.y = y; }
public Double getX() { return x; }
public void setX(Double x) { this.x = x; }
public Double getY() { return y; }
public void setY(Double y) { this.y = y; } }
private static final Function<Point, Double> X_GETTER; private static final Function<Point, Double> Y_GETTER; private static final BiConsumer<Point, Double> X_SETTER; private static final BiConsumer<Point, Double> Y_SETTER;
private static final MethodHandle X_GETTER_MH; private static final MethodHandle Y_GETTER_MH; private static final MethodHandle X_SETTER_MH; private static final MethodHandle Y_SETTER_MH;
private static final Method X_GETTER_METHOD; private static final Method Y_GETTER_METHOD; private static final Method X_SETTER_METHOD; private static final Method Y_SETTER_METHOD;
static { try { var lookup = MethodHandles.lookup();
X_GETTER_MH = lookup.findVirtual(Point.class, "getX", MethodType.methodType(Double.class)); Y_GETTER_MH = lookup.findVirtual(Point.class, "getY", MethodType.methodType(Double.class)); X_SETTER_MH = lookup.findVirtual(Point.class, "setX", MethodType.methodType(void.class, Double.class)); Y_SETTER_MH = lookup.findVirtual(Point.class, "setY", MethodType.methodType(void.class, Double.class));
X_GETTER = (Function<Point, Double>) LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(Function.class), MethodType.methodType(Object.class, Object.class), X_GETTER_MH, MethodType.methodType(Double.class, Point.class) ).getTarget().invokeExact(); Y_GETTER = (Function<Point, Double>) LambdaMetafactory.metafactory( lookup, "apply", MethodType.methodType(Function.class), MethodType.methodType(Object.class, Object.class), Y_GETTER_MH, MethodType.methodType(Double.class, Point.class) ).getTarget().invokeExact(); X_SETTER = (BiConsumer<Point, Double>) LambdaMetafactory.metafactory( lookup, "accept", MethodType.methodType(BiConsumer.class), MethodType.methodType(void.class, Object.class, Object.class), X_SETTER_MH, MethodType.methodType(void.class, Point.class, Double.class) ).getTarget().invokeExact(); Y_SETTER = (BiConsumer<Point, Double>) LambdaMetafactory.metafactory( lookup, "accept", MethodType.methodType(BiConsumer.class), MethodType.methodType(void.class, Object.class, Object.class), Y_SETTER_MH, MethodType.methodType(void.class, Point.class, Double.class) ).getTarget().invokeExact();
X_GETTER_METHOD = Point.class.getMethod("getX"); Y_GETTER_METHOD = Point.class.getMethod("getY"); X_SETTER_METHOD = Point.class.getMethod("setX", Double.class); Y_SETTER_METHOD = Point.class.getMethod("setY", Double.class); } catch (Throwable e) { throw new RuntimeException(e); } }
private final Point point = new Point(1.1, 2.2);
// 直接调用,作为基准性能 @Benchmark public Point plain() { Double x = point.getX(); Double y = point.getY(); point.setX(y); point.setY(x); return point; }
// static final声明的方法句柄 @Benchmark public Point methodHandle() throws Throwable { Double x = (Double) X_GETTER_MH.invokeExact(point); Double y = (Double) Y_GETTER_MH.invokeExact(point); X_SETTER_MH.invokeExact(point, y); Y_SETTER_MH.invokeExact(point, x); return point; }
// 使用LambdaMetafactory生成的lambda实例 @Benchmark public Point lambda() { Double x = X_GETTER.apply(point); Double y = Y_GETTER.apply(point); X_SETTER.accept(point, y); Y_SETTER.accept(point, x); return point; }
// 反射调用 @Benchmark public Point reflection() throws Throwable { Double x = (Double) X_GETTER_METHOD.invoke(point); Double y = (Double) Y_GETTER_METHOD.invoke(point); X_SETTER_METHOD.invoke(point, y); Y_SETTER_METHOD.invoke(point, x); return point; }
public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(LambdaBenchmark.class.getSimpleName()) .output("./lambda_benchmark.log") .forks(2) .build();
new Runner(opt).run(); }在笔者的Win11 24H2,i7-14700F下跑出的结果
Benchmark Mode Cnt Score Error UnitsLambdaBenchmark.lambda thrpt 200 6880990.881 ± 88188.596 ops/msLambdaBenchmark.methodHandle thrpt 200 6549743.700 ± 23655.703 ops/msLambdaBenchmark.plain thrpt 200 6527813.453 ± 23802.944 ops/msLambdaBenchmark.reflection thrpt 200 2009981.376 ± 8497.101 ops/ms这下给我整不会了,lambda的性能怎么比直接调用还高?跑了好几次都是差不多的结果,lambda、方法句柄、直接调用的性能相差不超过10%。
感谢梦佬提供的数据,在linux x86服务器的测试结果如下:

可以看到,三者的性能相差不超过1%,可以说明lambda的效率和直接调用无异!
登堂入室,通过SerializedLambda获取引用方法的信息
在MyBatis-Plus这样的现代ORM框架中,我们经常看到如下用法:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getName, "John");通过User::getName这个方法引用来指定数据库列,相比于硬编码"name"字符串,这种方式更加类型安全,当字段名name发生重构时,编译器会立刻报错。但是wrapper#eq方法接收到的只是一个函数式接口的实例,并不能获取到方法引用的信息。
我们深入源码会发现,LambdaQueryWrapper中接受方法引用的函数式接口(如SFunction)都继承了Serializable接口。因此,MyBatis-Plus正是通过Lambda序列化的特性,获取并调用我们传入的Lambda实例中的writeReplace方法,获取SerializedLambda对象,进而调用getImplMethodName()来拿到字符串"getName",再根据JavaBean规范推导出数据库列名"name"。从而解决了硬编码列名所造成的问题。
加深理解,invokedynamic魅力不止于此
通过indy指令,Java的Lambda表达式实现得既高效又灵活,充分体现了运行时优化的潜力。然而indy的魅力远不止于此,它已经成为现代Java诸多重要特性的基石:
- JDK 9的字符串拼接优化:
String a = s1 + s2;不再被编译为StringBuilder的调用,而是编译为一条indy指令,通过一系列操作实现比StringBuilder更好的性能 - JDK 21的switch模式匹配: 复杂的模式匹配规则也是通过
indy在底层实现的。
在后续的文章中,我们将继续揭秘这些特性。
到此,有的读者可能会认为,indy指令无法被普通开发者利用,更多是被一些建立于JVM之上的动态语言利用,比如Kotlin、Groovy。诚然,如果不利用ClassFile或者ASM这类字节码工具库,我们无法直接在代码中使用indy指令;如果你对字节码足够熟悉,利用这些工具库,普通开发者也可以间接利用indy的威力。例如梦佬就利用indy指令实现了一种比传统DCL模式性能更好的懒汉式单例:dreamlike-ocean/StableValue: 使用invokedynamic实现的零依赖StableValue用于取代DCL模式。
不过,我们可以利用一些小技巧来间接让一个indy指令指向我们自定义的BSM。下面将用一个例子来加深读者对indy指令的理解,用来结束这一篇文章我认为再好不过:
import java.lang.invoke.*;import java.util.function.Function;
/** * @author brooke_zb */public class CustomLambdaBootstrap { static void main() { printSomething("hello"); printSomething("custom"); printSomething("lambda"); }
private static void printSomething(String s) { Function<String, Integer> f = String::length; System.out.println(f.apply(s)); }
/** * BSM只规范了参数和返回值,只要遵循规范,我们也可以自定义一个BSM * 只不过现在我们无法直接让invokedynamic指令调用这个方法 * 所以我们将{@link java.lang.invoke.LambdaMetafactory#metafactory}方法的参数照搬一遍 * 准备来一个偷天换日 */ private static CallSite myBootstrap(MethodHandles.Lookup caller, String interfaceMethodName, MethodType factoryType, MethodType interfaceMethodType, MethodHandle implementation, MethodType dynamicMethodType) { System.out.printf("[Custom BSM] caller: %s\n", caller); System.out.printf("[Custom BSM] interfaceMethodName: %s\n", interfaceMethodName); System.out.printf("[Custom BSM] factoryType: %s\n", factoryType);
Function<String, Integer> f; if (Math.random() > 0.5) { f = new Function<>() { // 匿名内部类构造函数 { System.out.println("[Custom Function] choose impl 1!"); }
@Override public Integer apply(String s) { System.out.printf("apply string is: %s\n", s); return s.hashCode(); } }; } else { f = new Function<>() { { System.out.println("[Custom Function] choose impl 2!"); }
@Override public Integer apply(String s) { System.out.printf("reversed string is: %s\n", new StringBuilder(s).reverse()); return s.indexOf("o"); } }; }
MethodHandle fMh = MethodHandles.constant(Function.class, f); return new ConstantCallSite(fMh); }}以上代码编译后运行,如我们预想一样输出了三个字符串的长度:
566然后,为了更方便地修改这个类的字节码,我们需要用到IDEA的一个插件:jclasslib。安装后通过菜单打开刚刚这个类的字节码:

然后定位到BootstrapMethods区,点击序号0的Bootstrap方法中的链接跳转到常量表


然后编辑类名为我们这个类的路径,假设这个类位于study.invokedynamic包下,那么我们将类名设置为study/invokedynamic/CustomLambdaBootstrap。接着将方法名设置为我们编写的自定义BSM方法:myBootstrap。
然后点击左上角的保存图标,选择Overwrite。完成后,多跑几次程序,我们便能得到以下两种结果:
[Custom BSM] caller: study.invokedynamic.CustomLambdaBootstrap[Custom BSM] interfaceMethodName: apply[Custom BSM] factoryType: ()Function[Custom Function] choose impl 1!apply string is: hello99162322apply string is: custom-1349088399apply string is: lambda-1110092857[Custom BSM] caller: study.invokedynamic.CustomLambdaBootstrap[Custom BSM] interfaceMethodName: apply[Custom BSM] factoryType: ()Function[Custom Function] choose impl 2!reversed string is: olleh4reversed string is: motsuc4reversed string is: adbmal-1通过这个例子,相信能让读者们更深刻地理解indy指令的特性:
- 运行期第一次执行时调用BSM,动态绑定调用点
- 后续执行无需引导,直接调用已绑定的调用点,性能更高
- 更加灵活,支持各种动态场景,而非编译期写死