JVM 不支持 Java 语言的动态类型,而是通过 invokedynamic 指令、引导方法(BSM)和 MethodHandle 为 Groovy、JRuby 等动态语言提供运行时方法链接能力,Java 源码仍严格遵循静态类型规则。

Java 语言本身不支持动态类型变量,JVM 也从未改变这一事实。所谓“JVM 支持动态类型语言”,指的是它为 Groovy、JRuby、JavaScript(GraalVM)等运行在 JVM 上的动态语言 提供了底层执行能力,核心机制是 invokedynamic 指令 + 引导方法(BSM)+ MethodHandle,而非让 Java 源码能声明 var x = ... 这类无类型变量。
invokedynamic 不绑定具体方法,只预留调用入口
与 invokevirtual、invokestatic 等指令不同,invokedynamic 字节码中不包含目标方法的符号引用(比如 Class 名、方法签名)。它只存两个关键信息:
- 一个引导方法(Bootstrap Method)在常量池中的索引
- 一组静态参数(如方法名、期望的 MethodType、额外元数据)
真正的方法定位、生成、缓存逻辑,全部推迟到首次执行该指令时由引导方法决定。这意味着:类型判断、接收者查找、重载解析等原本编译期完成的事,现在交由运行时代码控制。
引导方法(BSM)才是动态逻辑的实际执行者
BSM 是普通 Java 方法,但签名固定:
public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type, Object... staticArgs)
它的返回值必须是 CallSite(如 ConstantCallSite),其中封装了一个 MethodHandle —— 即最终要调用的目标。
- Java 8 编译 Lambda 时,BSM 调用 LambdaMetafactory.metafactory,动态生成私有静态方法和实现类,再包装成 MethodHandle
- Groovy 的 BSM 会根据运行时对象的实际 class 和方法名,查 MetaClass 或缓存 MetaMethod
- JRuby 的 BSM 可能结合 call site profiling 做多态内联,甚至跳过反射直接调用 C 实现
换句话说:invokedynamic 本身不“动态”,它只是把动态性外包给了你写的(或编译器生成的)BSM。
MethodHandle 提供类型安全的底层调用能力
java.lang.invoke.MethodHandle 是 JVM 层级的“函数指针”,比反射更轻量、更可控:
- 它绕过访问检查(可被 Lookup 权限授权),避免反射的 SecurityManager 开销
- 参数和返回值类型在 MethodType 中严格声明,JVM 可做类型适配(自动装箱/解包、基本类型转换)
- JIT 编译器能对 MethodHandle 调用链做深度内联优化,而 Method.invoke() 几乎无法内联
动态语言运行时正是靠大量 MethodHandle 组合(guard、filter、fold)来构建灵活的调用链,实现类似 a.foo() 这种语句在运行时按 receiver 类型分发的行为。
Java 源码仍受静态类型约束,动态性止于字节码层
哪怕你用 javac 编译一个含 Lambda 的 Java 类,源码中所有变量、参数、返回值都必须带明确类型。javac 只是在生成字节码时,用 invokedynamic 替代了旧式匿名类方案。
- 你不能写
var x = getSomething(); x.doStuff();并指望 JVM 在运行时根据 x 的实际值类型去查 doStuff —— Java 编译器会在编译期报错 - JS 或 Python 在 JVM 上运行时,其解释器/编译器自己构造 BSM 和 MethodHandle,处理
x.doStuff();这个过程对 Java 代码完全透明
JVM 的角色是提供一套可组合、可优化、可定制的动态链接原语,而不是修改 Java 语言的类型系统。

















