Skip to main content

Command Palette

Search for a command to run...

Java Lambda 表达式

Published
2 min readView as Markdown

1. 背景

Java 8 引入了 Lambda 表达式和函数式接口,其基础技术是 invokedynamic 指令和 LambdaMetafactory 动态类生成机制。相比于传统的匿名内部类和反射,它具有更好的性能和编译器可分析性。(对于编译器跟JVM虚拟机来说,对于确定性的内容更容易优化,比如内联、分支预测、逃逸分析等等)

2. Lambda 是如何被实现的?

int num = 10;
Runnable r = () -> System.out.println(num);//Lambda语法糖

Java 编译器会将 Lambda 表达式 desugar 成调用 invokedynamic 指令,该指令的 Bootstrap 方法通常是 LambdaMetafactory.metafactoryaltMetafactory

2.1. 编译产物:invokedynamic

0: bipush        10
2: istore_1                    // int num = 10;
4: aload_0                     // 加载 this
5: iload_1                     // 加载局部变量 num(被捕获),这就是为什么lambda表达式中的变量必须是final的
6: invokedynamic #0, run()Ljava/lang/Runnable;

2.2. 实现方式

这些方法会根据传入的 MethodHandle(即 Lambda 实现的方法)生成一个实现某个函数式接口的匿名类,并缓存在 CallSite 中。

// 编译后 roughly 等价于:
CallSite site = LambdaMetafactory.metafactory(...);
Runnable r = (Runnable) site.getTarget().invoke();

3. 什么是 MethodHandle?

MethodHandle 是 Java 层面对方法、构造器、字段的直接引用,是 java.lang.invoke 的一部分。

MethodHandle mh = lookup.findVirtual(String.class, "length", methodType(int.class));// 这里在类加载时,调用的方法签名实际上就已经确定了,所以很容易做jvm通用的优化

int len = (int) mh.invokeExact("hello");

和反射不同,它:

  • 类型安全

  • 支持高性能调用

  • 能与 invokedynamic 联动做深层优化

对比下反射

Method m = String.class.getMethod("length");
int len = (int) m.invoke("abc"); // JIT 无法预知调用目标,只有运行时才能确定的知道需要调用的对象,很难提前优化

MethodHandle vs Reflection

特性MethodHandleReflection
可内联优化✅ 是❌ 否
权限检查开销✅ 少❌ 多(每次都要)
类型安全✅ 静态校验(invokeExact)❌ 动态检查
参数处理✅ 无需数组❌ 参数统一打包为 Object[]
调用速度✅ 快❌ 慢

4. JVM 是怎么优化 invokeExact 的?

HotSpot 中的 C2 编译器(优化级别最高的 JIT 编译器):

  • 直接识别 invokeExactMethodHandle intrinsic 调用

  • 能观察到目标方法(例如 String.length 是 final 的,没有虚调用成本)

  • 直接内联调用目标

  • 如果在逃逸分析后发现 MethodHandle 是常量,可以去掉中间对象

JVM 可以把 MethodHandle.invokeExact 优化得跟直接写 str.length() 一样。Lambda 表达式的序列化风险

5. Lambda 表达式的序列化风险

5.1.1. Lambda 表达式默认不可序列化

只有在函数式接口扩展了 java.io.Serializable 时,lambda 才可能被序列化。

@FunctionalInterface
public interface Task extends Serializable {
    void run();
}

Task task = () -> System.out.println("Serializable lambda");

5.1.2. SerializedLambda

lambda表达式序列化的是一个 SerializedLambda 实例,而非真实可执行类

//这意味着 反序列化时必须能加载原实现类,找到精确的方法,甚至方法签名也不能变,否则会失败。
public interface SerializedLambda extends Serializable {
    // capture context
    String getCapturingClass();

    // SAM descriptor
    String getDescriptorClass();
    String getDescriptorName();
    String getDescriptorMethodType();

    // implementation
    int getImplReferenceKind();
    String getImplClass();
    String getImplName();
    String getImplMethodType();

    // dynamic args -- these will individually need to be Serializable too
    Object[] getCapturedArgs();
}

More from this blog

Spring Batch 事务处理技术分享

一、概述 Spring Batch 是一个为大规模批处理应用提供的框架,常用于处理高性能的离线任务,如数据导入、数据清洗、报表生成等。在 Spring Batch 中,事务处理是一个核心功能,它确保了批处理任务的原子性、一致性、隔离性和持久性(ACID 属性),并帮助处理大规模数据时的可靠性和容错性。 事务管理在 Spring Batch 中是通过与 Spring 的事务管理框架集成来实现的。Spring Batch 在执行作业的每个步骤时,都会在事务内执行,以确保作业的成功执行和数据的一致性。...

Feb 13, 20252 min read

Java core

9 posts