Java Lambda 表达式
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.metafactory 或 altMetafactory。
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
| 特性 | MethodHandle | Reflection |
| 可内联优化 | ✅ 是 | ❌ 否 |
| 权限检查开销 | ✅ 少 | ❌ 多(每次都要) |
| 类型安全 | ✅ 静态校验(invokeExact) | ❌ 动态检查 |
| 参数处理 | ✅ 无需数组 | ❌ 参数统一打包为 Object[] |
| 调用速度 | ✅ 快 | ❌ 慢 |
4. JVM 是怎么优化 invokeExact 的?
HotSpot 中的 C2 编译器(优化级别最高的 JIT 编译器):
直接识别
invokeExact是MethodHandleintrinsic 调用能观察到目标方法(例如
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();
}