bigywater.tech
Java · 168

invokedynamic:动态调用点如何被 JVM 重写

javac 只在 lambda、字符串拼接等地方生成 invokedynamic,但它是 JVM 提供给所有语言的通用调用点机制。这篇自己生成字节码,把 bootstrap 方法、CallSite 的链接时机和可变目标直接做出来(lambda 本身的实现见前面 Lambda 一篇)。

1 次
一个调用点无论执行多少次,bootstrap 只被调用的次数
setTarget
MutableCallSite 换目标,不重新链接
BootstrapMethodError
bootstrap 抛异常时的结果(失败也被缓存)

链接时机

实测 · 本机执行输出
  还没有调用任何方法时 bootstrap 被调用的次数:0
  const1 调用 3 次之后 bootstrap 调用记录:[const1]
  const2 再调用 2 次之后 bootstrap 调用记录:[const1, const2](每个调用点只链接一次)

可变调用点

实测 · 本机执行输出
  初始目标 v1:v1:x,bootstrap 记录 [mut1]
  MutableCallSite.setTarget(v2) 之后,同一个调用点再调用:v2:x,bootstrap 记录 [mut1](没有重新链接,只是换了目标)

bootstrap 失败

实测 · 本机执行输出
  第 1 次调用:BootstrapMethodError,cause = IllegalStateException: bootstrap 故意失败:bad
  第 2 次调用:BootstrapMethodError,cause = 无
  bootstrap 被调用的次数 = 1(失败的链接被缓存,同一个调用点不会重试)

类文件里的调用点

实测 · 本机执行输出
  const1 方法体:
    0: aload_0
    1: invokedynamic #15, 0 // InvokeDynamic #0:const1:(Ljava/lang/String;)Ljava/lang/String;
    6: areturn
  常量池里的 InvokeDynamic 项:
    #15 = InvokeDynamic #0:#14 // #0:const1:(Ljava/lang/String;)Ljava/lang/String;
    #18 = InvokeDynamic #0:#17 // #0:const2:(Ljava/lang/String;)Ljava/lang/String;
  BootstrapMethods 属性:
    0: #13 REF_invokeStatic Main.bsm:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
    Method arguments:

分步演示

invokedynamic 调用点
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,ASM 9.7(Maven 解析)
    • 没有验证:invokedynamic 调用点的运行开销(我试过一个手写计时,结果被 MethodHandle 调用本身的开销主导,没有采用)、VolatileCallSite、静态参数(bootstrap 的常量参数)与 condy、JIT 对 MutableCallSite 的内联与去优化、JDK 17 与 27 的差异
    京ICP备2026067502号-1京公网安备11010502063487号