08.深入解密JVM invokedynamic机制——方法句柄与LambdaForm(上)

图片[1]-08.深入解密JVM invokedynamic机制——方法句柄与LambdaForm(上)-速优课

本文原创,转载请注明出处。关注博主,持续分享Java进阶知识。

一、开篇:动态语言的”赛马”难题

先来看一个有趣的场景:

class Horse {
    public void race() {
        System.out.println("Horse.race()");
    }
}
​
class Deer {
    public void race() {
        System.out.println("Deer.race()");
    }
}
​
class Cobra {
    public void race() {
        System.out.println("How do you turn this on?");
    }
}

问题:如何用同一种方式调用它们的赛跑方法?

在传统的Java方法调用中,我们需要明确指定目标方法的类名:

// 方式1:直接调用(需要知道具体类型)
Horse horse = new Horse();
horse.race();
​
// 方式2:反射调用(复杂且低效)
Object obj = new Deer();
obj.getClass().getMethod("race").invoke(obj);

这两种方式都不够优雅。为此,Java 7引入了invokedynamic指令,为动态语言提供了更灵活的方法调用机制。


二、方法句柄(MethodHandle):动态调用的基石

2.1 方法句柄的定义

方法句柄是一个强类型的、可直接执行的引用,它可以指向:

  • 静态方法
  • 实例方法
  • 构造器
  • 字段(getter/setter)

核心特点:

  • 只关心参数类型和返回类型(MethodType)
  • 不关心方法所在的类和方法名
  • 权限检查在创建阶段完成,调用时无需检查

2.2 方法句柄的类型(MethodType)

// 创建方法句柄类型:返回类型 + 参数类型列表
MethodType t = MethodType.methodType(void.class, Object.class);
// 表示:接收一个Object参数,返回void的方法

方法句柄类型是适配的唯一关键!

2.3 创建方法句柄

class Foo {
    private static void bar(Object o) { ... }
    
    public static Lookup lookup() {
        return MethodHandles.lookup();
    }
}
​
// 获取方法句柄的方式1:从反射Method转换
MethodHandles.Lookup l = Foo.lookup();
Method m = Foo.class.getDeclaredMethod("bar", Object.class);
MethodHandle mh0 = l.unreflect(m);
​
// 获取方法句柄的方式2:直接查找
MethodType t = MethodType.methodType(void.class, Object.class);
MethodHandle mh1 = l.findStatic(Foo.class, "bar", t);

2.4 方法句柄的权限模型

关键区别于反射:

特性方法句柄反射
权限检查时机创建时调用时
重复调用开销每次调用都检查
访问权限来源Lookup对象的创建位置当前调用位置

示例:突破私有方法访问

class PrivateClass {
    private void secret() {
        System.out.println("Secret method!");
    }
    
    // 在类内部获取Lookup对象
    public static Lookup getLookup() {
        return MethodHandles.lookup();
    }
}
​
// 在外部调用私有方法
Lookup lookup = PrivateClass.getLookup();
MethodHandle mh = lookup.findPrivate(PrivateClass.class, "secret", 
                                     MethodType.methodType(void.class));
mh.invokeExact(new PrivateClass());  // 成功调用!

三、方法句柄的调用方式

3.1 invokeExact:严格类型匹配

public final native @PolymorphicSignature Object invokeExact(Object... args) throws Throwable;

签名多态性(Signature Polymorphism):

  • 根据传入参数的声明类型生成方法描述符
  • 参数类型必须严格匹配,否则抛出异常
public void test(MethodHandle mh, String s) throws Throwable {
    // mh的类型是(Object)void
    mh.invokeExact(s);              // 编译时生成(String)void描述符 → 运行时异常!
    mh.invokeExact((Object) s);     // 编译时生成(Object)void描述符 → 正确!
}

对应的字节码:

public void test(MethodHandle, String) throws java.lang.Throwable;
  Code:
     0: aload_1
     1: aload_2
     2: invokevirtual MethodHandle.invokeExact:(Ljava/lang/String;)V  // 类型不匹配
     5: aload_1
     6: aload_2
     7: invokevirtual MethodHandle.invokeExact:(Ljava/lang/Object;)V  // 类型匹配
    10: return

3.2 invoke:自动类型适配

mh.invoke(s);  // 自动将String适配为Object

原理: invoke会调用MethodHandle.asType()生成适配器,对参数进行类型转换。

3.3 方法句柄的参数操作

操作API说明
asType()类型适配,生成适配器
dropArguments()抛弃部分参数
bindTo()绑定固定参数,实现柯里化

柯里化示例:

// f(x, y) → g(y) = f(4, y)
MethodType originalType = MethodType.methodType(int.class, int.class, int.class);
MethodHandle original = lookup.findStatic(Math.class, "addExact", originalType);

// 绑定第一个参数为4
MethodHandle curried = original.bindTo(4);

// 调用时只需传入第二个参数
int result = (int) curried.invokeExact(5);  // 结果:4 + 5 = 9

四、方法句柄的实现机制

4.1 调用链路分析

import java.lang.invoke.*;

public class Foo {
    public static void bar(Object o) {
        new Exception().printStackTrace();
    }

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType t = MethodType.methodType(void.class, Object.class);
        MethodHandle mh = l.findStatic(Foo.class, "bar", t);
        mh.invokeExact(new Object());
    }
}

正常栈轨迹:

java.lang.Exception
        at Foo.bar(Foo.java:5)
        at Foo.main(Foo.java:12)

启用隐藏栈帧后:

java.lang.Exception
        at Foo.bar(Foo.java:5)
        at java.lang.invoke.DirectMethodHandle$Holder.invokeStatic(...)
        at java.lang.invoke.LambdaForm$MH000/766572210.invokeExact_MT000_LLL_V(...)
        at Foo.main(Foo.java:12)

4.2 LambdaForm:方法句柄的核心适配器

共享适配器(初始状态):

final class java.lang.invoke.LambdaForm$MH000 {
    static void invokeExact_MT000_LLLLV(Object, Object, Object, Object) {
        // 1. 类型检查
        checkcast #14 // class java/lang/invoke/MethodHandle
        invokestatic #22 // checkExactType()
        
        // 2. 自定义优化检查(调用次数超过阈值时触发)
        invokestatic #26 // checkCustomized()
        
        // 3. 调用底层方法
        invokevirtual #30 // invokeBasic()
        return
    }
}

自定义适配器(调用次数超过127次后):

final class java.lang.invoke.LambdaForm$DMH000 {
    static void invokeStatic000_LL_V(Object, Object) {
        // 将方法句柄作为常量,跳过类型检查
        ldc #14 // CONSTANT_PLACEHOLDER_1 <<Foo.bar(Object)void/invokeStatic>>
        
        // 直接获取MemberName并调用
        invokestatic #28 // linkToStatic()
        return
    }
}

4.3 MemberName:方法的内部表示

final class MemberName implements Member, Cloneable {
    // JVM注入的字段
    // JVM_Method* vmtarget;  // 方法地址
    // int         vmindex;    // 方法表索引
}

linkToStatic的特殊处理:

  • JVM对linkToStatic调用做特殊处理
  • 根据MemberName中的方法地址或方法表索引,直接跳转到目标方法

五、方法句柄与反射的对比

特性方法句柄反射
类型检查编译时运行时
权限检查创建时每次调用
性能开销较低(可内联)较高(不可内联)
灵活性高(支持柯里化等)中等
适用场景动态语言实现、Lambda通用反射操作

六、实战:方法句柄的性能优化

6.1 测试代码

public class Foo {
    public void bar(Object o) {}

    public static void main(String[] args) throws Throwable {
        MethodHandles.Lookup l = MethodHandles.lookup();
        MethodType t = MethodType.methodType(void.class, Object.class);
        MethodHandle mh = l.findVirtual(Foo.class, "bar", t);

        long current = System.currentTimeMillis();
        for (int i = 1; i <= 2_000_000_000; i++) {
            if (i % 100_000_000 == 0) {
                long temp = System.currentTimeMillis();
                System.out.println(temp - current);
                current = temp;
            }
            mh.invokeExact(new Foo(), new Object());
        }
    }
}

6.2 优化方案

方案1:将方法句柄声明为常量

public class Foo {
    private static final MethodHandle MH_BAR;
    
    static {
        try {
            MethodHandles.Lookup l = MethodHandles.lookup();
            MethodType t = MethodType.methodType(void.class, Object.class);
            MH_BAR = l.findVirtual(Foo.class, "bar", t);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    public void bar(Object o) {}
    
    public static void main(String[] args) throws Throwable {
        // 使用常量方法句柄
        for (int i = 1; i <= 2_000_000_000; i++) {
            MH_BAR.invokeExact(new Foo(), new Object());
        }
    }
}

方案2:使用invokeBasic(跳过类型检查)

// 需要先将方法句柄适配为正确类型
MethodHandle mh = l.findVirtual(Foo.class, "bar", t);
mh.invokeBasic(new Foo(), new Object());  // 无类型检查

6.3 思考问题

  1. 为什么将方法句柄声明为常量可以提升性能?
  2. invokeBasic和invokeExact的区别是什么?
  3. 方法句柄调用能否被JIT内联?为什么?

七、总结

核心要点回顾

  1. 方法句柄:强类型、可执行的方法引用,只关心参数类型和返回类型
  2. 签名多态性:invokeExact/invoke根据传入参数的声明类型生成方法描述符
  3. 权限模型:权限检查在创建阶段完成,调用时无需检查
  4. LambdaForm:方法句柄调用的核心适配器,支持共享和自定义两种模式
  5. MemberName:方法的内部表示,包含方法地址或方法表索引
  6. 柯里化:通过bindTo操作实现参数绑定

关键理解

  • 方法句柄是invokedynamic的底层基石
  • 与反射相比,方法句柄性能更高,因为权限检查在创建时完成
  • JIT可以将方法句柄调用内联,但需要将其识别为常量
  • 方法句柄支持灵活的参数操作(增删改)

写在最后

方法句柄是Java动态调用机制的核心,它为invokedynamic和Lambda表达式提供了底层支持。深入理解方法句柄的实现原理,对于掌握Java的动态特性至关重要。下一篇,我将详细介绍invokedynamic指令的具体实现,以及它如何支撑Lambda表达式的运行。

如果觉得本文对你有帮助,欢迎点赞、收藏、关注!后续我会继续分享更多JVM相关的深度解析文章。

📚 关注我,一起深入探索Java虚拟机的奥秘!

© 版权声明
THE END
喜欢就支持一下吧
点赞6
相关推荐
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容

温馨提示:
1、本内容转载于网络,版权归原作者所有!
2、本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
3、本内容若侵犯到你的版权利益,请联系我们,会尽快给予删除处理!