当前位置: 首页 > ds >正文

JVM——JVM是怎么实现invokedynamic的?

JVM是怎么实现invokedynamic的?

在Java 7引入invokedynamic之前,Java虚拟机(JVM)在方法调用方面相对较为“僵化”。传统的Java方法调用主要依赖于invokestatic、invokespecial、invokevirtual和invokeinterface这四条指令,每条指令都明确绑定了目标方法的类名、方法名和方法描述符。这种绑定方式虽然稳定,但对于动态语言的支持却显得力不从心。动态语言强调“鸭子类型”(duck typing),即只要对象表现出某种行为,就认为它符合某种类型,而不必显式继承某个类或实现某个接口。为了打破这种限制,Java 7引入了invokedynamic指令,为JVM上的动态语言支持铺平了道路,同时也为Java自身的语言特性发展(如Lambda表达式)提供了强有力的支持。

invokedynamic指令的基本概念

invokedynamic指令的核心在于引入了“调用点”(CallSite)这一概念。调用点是一个抽象类,它将方法调用与目标方法的链接推迟到运行时进行。每个invokedynamic指令在执行时都会绑定一个调用点对象,该对象负责在运行时确定要调用的目标方法。调用点对象可以是ConstantCallSite(不可变调用点)、MutableCallSite(可变调用点)或VolatileCallSite(线程安全可变调用点)。通过调用点,JVM能够在运行时灵活地选择目标方法,从而支持动态类型语言的灵活调用机制。

import java.lang.invoke.*;
​
public class ConstantCallSiteDemo {public static void main(String[] args) throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();MethodType methodType = MethodType.methodType(void.class);MethodHandle methodHandle = lookup.findStatic(ConstantCallSiteDemo.class, "hello", methodType);CallSite callSite = new ConstantCallSite(methodHandle);((ConstantCallSite) callSite).dynamicInvoker().invokeExact();}
​public static void hello() {System.out.println("Hello, World!");}
}

invokedynamic的底层实现

(一)启动方法(Bootstrap Method)

当JVM第一次遇到invokedynamic指令时,它会执行该指令关联的启动方法。启动方法是一个特殊的静态方法,它负责创建并返回一个CallSite对象。启动方法的第一个参数必须是MethodHandles.Lookup对象,用于提供对类成员的访问权限;第二个参数是目标方法的名称(String类型);第三个参数是MethodType,表示目标方法的类型。除此之外,启动方法还可以接受其他参数,用于辅助生成CallSite对象。

import java.lang.invoke.*;
​
public class BootstrapDemo {public static void main(String[] args) throws Throwable {// 创建方法句柄MethodHandles.Lookup lookup = MethodHandles.lookup();MethodType type = MethodType.methodType(void.class);MethodHandle target = lookup.findStatic(BootstrapDemo.class, "hello", type);
​// 创建调用点CallSite callSite = new ConstantCallSite(target);
​// 获取动态调用的入口点MethodHandle dynamicInvoker = callSite.dynamicInvoker();
​// 动态调用dynamicInvoker.invokeExact();}
​public static void hello() {System.out.println("Hello, Invokedynamic!");}
}

启动方法返回一个CallSite对象,这个对象将与invokedynamic指令绑定,并在后续调用中直接使用。

(二)动态链接

调用点对象中的动态链接过程涉及到方法句柄(MethodHandle)的使用。方法句柄是一个强类型的引用,可以直接执行。它可以指向静态方法、实例方法、构造函数,甚至是字段的getter和setter方法(在方法句柄中表现为虚构方法)。与反射不同,方法句柄的权限检查在创建时完成,后续调用无需重复检查,因此性能更高。

import java.lang.invoke.*;
​
public class MethodHandleDemo {public static void main(String[] args) throws Throwable {// 创建方法句柄MethodHandles.Lookup lookup = MethodHandles.lookup();MethodType type = MethodType.methodType(void.class);MethodHandle methodHandle = lookup.findStatic(MethodHandleDemo.class, "hello", type);
​// 调用方法句柄methodHandle.invokeExact();}
​public static void hello() {System.out.println("Hello, Method Handle!");}
}

(三)Lambda 表达式与 invokedynamic

Lambda 表达式的出现是 Java 8 的一个重大更新,它允许开发者以更简洁的方式编写匿名类。Lambda 表达式的实现正是基于 invokedynamic 指令。当编译器遇到 Lambda 表达式时,会将其转换为一个函数式接口的实例。这个转换过程通过 invokedynamic 指令完成,编译器会生成一个 bootstrap 方法,该方法在运行时生成一个适配器类,实现对应的函数式接口。

例如,以下代码:

Comparator<String> comparator = (a, b) -> a.compareTo(b);

编译器会将其转换为类似如下的 invokedynamic 指令:

aload_1
invokedynamic #5, 0 // BootstrapMethod

对应的 bootstrap 方法会生成一个实现 Comparator 接口的适配器类,并返回该类的实例。

import java.lang.invoke.*;
​
public class LambdaBootstrap {public static void main(String[] args) throws Throwable {MethodHandles.Lookup lookup = MethodHandles.lookup();MethodHandle mh = lookup.findStatic(LambdaBootstrap.class, "lambdaImpl", MethodType.methodType(void.class, String.class));CallSite callSite = new ConstantCallSite(mh.asType(MethodType.methodType(void.class, Object.class)));((ConstantCallSite) callSite).dynamicInvoker().invokeExact("Hello, Lambda!");}
​private static void lambdaImpl(String s) {System.out.println(s);}
}

三、invokedynamic的性能分析

(一)方法句柄的性能

方法句柄的性能在某些场景下接近直接方法调用。通过将方法句柄存储在 final 静态变量中,即时编译器可以对其进行内联优化,从而消除方法句柄调用的开销。然而,如果方法句柄被频繁更新或无法被识别为常量,其性能可能接近反射调用,存在一定的性能开销。

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
​
public class MethodHandlePerformance {private static final MethodHandle MH;
​static {try {MethodHandles.Lookup lookup = MethodHandles.lookup();MethodType type = MethodType.methodType(void.class, int.class);MH = lookup.findStatic(MethodHandlePerformance.class, "target", type);} catch (Throwable e) {throw new RuntimeException(e);}}
​public static void main(String[] args) throws Throwable {long start = System.currentTimeMillis();for (int i = 0; i < 1000000000; i++) {MH.invokeExact(42);}System.out.println("MethodHandle: " + (System.currentTimeMillis() - start) + " ms");}
​public static void target(int i) {// 空方法}
}

(二)Lambda 表达式的性能

Lambda 表达式的性能在大多数情况下接近直接方法调用。对于未捕获变量的 Lambda 表达式,即时编译器可以内联其调用,性能与直接调用几乎无差异。而对于捕获变量的 Lambda 表达式,即时编译器的逃逸分析可以优化掉适配器实例的创建,使其性能接近未捕获变量的 Lambda 表达式。然而,在逃逸分析无法生效的情况下,可能会产生适配器实例的创建开销,性能会有所下降。

import java.util.function.IntConsumer;
​
public class LambdaPerformance {public static void main(String[] args {int x = 42;long start = System.currentTimeMillis();for (int i = 0; i < 1000000000; i++) {((IntConsumer) (j) -> {// 空实现}).accept(42);}System.out.println("Lambda: " + (System.currentTimeMillis() - start) + " ms");}
}

invokedynamic的实际应用与优势

(一)动态语言支持

invokedynamic为动态语言在JVM上的实现提供了基础支持。动态语言(如Groovy、JavaScript等)可以利用invokedynamic实现高效的动态方法调用,而无需通过反射或复杂的桥接代码。例如,在Groovy中,方法调用可以通过invokedynamic直接链接到目标方法,而无需显式的类型检查和方法查找,从而提高性能。

// Groovy示例
def hello(name) {println "Hello, $name!"
}hello "Invokedynamic"

(二)Java 8 的 Lambda 表达式

如前所述,Java 8 的 Lambda 表达式借助 invokedynamic 实现了简洁高效的语法糖。Lambda 表达式可以被转化为函数式接口的实例,而无需显式地实现接口。这不仅简化了代码,还提高了性能,因为 invokedynamic 允许即时编译器对 Lambda 表达式进行内联优化。

import java.util.function.Consumer;public class LambdaExample {public static void main(String[] args) {Consumer<String> consumer = (s) -> {System.out.println(s);};consumer.accept("Hello, Lambda!");}
}

(三)函数式编程

invokedynamic 支持函数式编程风格,使得 Java 开发者能够更方便地编写函数式代码。通过 Lambda 表达式和方法引用,开发者可以将行为(函数)作为参数传递给方法,或者将方法的结果作为函数返回。这种编程风格在处理集合操作(如流式 API)时尤为强大。

import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;public class FunctionalExample {public static void main(String[] args) {List<String> names = Arrays.asList("Alice", "Bob", "Charlie");Consumer<String> printer = (name) -> System.out.println(name);names.forEach(printer);}
}

总结

invokedynamic 指令作为 Java 7 引入的一项革命性特性,为 JVM 带来了前所未有的灵活性和动态性。通过调用点(CallSite)和方法句柄(MethodHandle)的机制,invokedynamic 允许在运行时动态确定方法调用的目标,从而打破了传统方法调用的静态绑定限制。这不仅为动态语言在 JVM 上的高效实现铺平了道路,也为 Java 自身的语言发展注入了新的活力,使得诸如 Lambda 表达式等现代编程特性得以实现。在性能方面,尽管 invokedynamic 在某些复杂场景下可能存在一定的开销,但即时编译器的优化(如内联和逃逸分析)在大多数情况下能够使其性能接近甚至媲美直接方法调用。对于开发者而言,理解 invokedynamic 的工作原理有助于更好地利用 Java 8 及更高版本中的新特性,编写出更简洁、高效且具有函数式风格的代码,同时也为探索 JVM 上的动态语言世界打开了一扇大门。

http://www.xdnf.cn/news/4009.html

相关文章:

  • HttpPrinter 是一款功能强大的跨平台 Web 打印解决方案
  • C与指针——结构与联合
  • Feign的原理
  • cesium基础设置
  • xx外卖知识补充
  • 日语学习-日语知识点小记-进阶-JLPT-N1阶段(1):语法单词
  • Jetpack Compose 边距终极指南:Margin 和 Padding 的正确处理方式
  • 详细案例,集成算法
  • 高等数学第三章---微分中值定理与导数的应用(3.3泰勒(Taylor)公式)
  • JAVA组织/岗位拉取多段时间属性到一张表上时,时间段分隔问题
  • 解释一下NGINX的反向代理和正向代理的区别?
  • 【C++重载操作符与转换】下标操作符
  • Android学习总结之事件分发机制篇
  • Java大厂面试:Java技术栈中的核心知识点
  • 25.5.4数据结构|哈夫曼树 学习笔记
  • 深度学习在自动驾驶车辆车道检测中的应用
  • 硬件工程师面试常见问题(13)
  • 一个整数n可以有多种分划,分划的整数之和为n,在不区分分划出各整数的次序时,字典序递减输出n 的各详细分划方案和分划总数,详解
  • 5.4学习记录
  • 洛谷 P2473 [SCOI2008] 奖励关
  • TS 类型别名
  • ES6入门---第三单元 模块一:类、继承
  • 【操作系统】死锁
  • [pdf,epub]292页《分析模式》漫谈合集01-59提供下载
  • 【C语言入门级教学】VS使用调试技巧1
  • 算法笔记.求约数
  • 303.整数拆分
  • Seata TCC 实战笔记:从零搭建分布式事务 Demo (含源码)
  • Linux的时间同步服务器
  • 【LLM】deepseek R1之GRPO训练笔记(持续更新)