什么是 invokedynamic 以及如何使用它?

发布于 2024-11-19 10:01:11 字数 92 浏览 0 评论 0 原文

我不断听说 JVM 中添加了所有新的很酷的功能,其中一个很酷的功能就是 invokedynamic。我想知道它是什么以及它如何使 Java 中的反射编程变得更容易或更好?

I keep hearing about all the new cool features that are being added to the JVM and one of those cool features is invokedynamic. I would like to know what it is and how does it make reflective programming in Java easier or better?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

苦行僧 2024-11-26 10:01:11

它是一个新的 JVM 指令,允许编译器生成代码,该代码调用比以前可能更宽松的规范的方法 - 如果您知道“这里是一篇非常精彩的博客文章,提供了很多细节。

It is a new JVM instruction which allows a compiler to generate code which calls methods with a looser specification than was previously possible -- if you know what "duck typing" is, invokedynamic basically allows for duck typing. There's not too much you as a Java programmer can do with it; if you're a tool creator, though, you can use it to build more flexible, more efficient JVM-based languages. Here is a really sweet blog post that gives a lot of detail.

绅刃 2024-11-26 10:01:11

作为我的 Java Records 文章的一部分,我阐述了Invoke Dynamic 背后的动机。让我们从印地的粗略定义开始。

介绍 Indy


Invoke Dynamic(也称为 Indy)是 JSR 292 旨在增强 JVM 对动态类型语言的支持。在 Java 7 中首次发布之后,invokedynamic 操作码及其 java.lang.invoke 包被 JRuby 等基于 JVM 的动态语言广泛使用。

尽管 indy 专门设计用于增强动态语言支持,但它提供的功能远不止于此。事实上,它适合在任何语言设计者需要任何形式的动态性的地方使用,从动态类型杂技到动态策略!

例如,Java 8 Lambda 表达式实际上是使用 invokedynamic 实现的,尽管 Java 是一种静态类型语言!

用户自定义字节码


很长一段时间以来,JVM确实支持四种方法调用类型:invokestatic调用静态方法,invokeinterface调用接口方法,invokespecial< /code> 调用构造函数、super() 或私有方法,invokevirtual 调用实例方法。

尽管存在差异,这些调用类型有一个共同的特征:我们无法用自己的逻辑来丰富它们。相反,invokedynamic 使我们能够以任何我们想要的方式引导调用过程。然后 JVM 负责直接调用 Bootstrapped 方法。

Indy 是如何工作的?


JVM 第一次看到 invokedynamic 指令时,它会调用一个名为 的特殊静态方法引导方法。 bootstrap 方法是我们编写的一段 Java 代码,用于准备实际要调用的逻辑:

在此处输入图像描述

然后 bootstrap 方法返回 java.lang.invoke.CallSite 的实例。此CallSite 保存对实际方法的引用,即MethodHandle

从现在开始,每次 JVM 再次看到此 invokedynamic 指令时,它都会跳过慢速路径并直接调用底层可执行文件。除非发生变化,否则 JVM 会继续跳过慢速路径。

示例:Java 14 Records


Java 14 Records 提供了一种很好的紧凑语法来声明应该是哑数据持有者的类。

考虑这个简单的记录:

public record Range(int min, int max) {}

此示例的字节码类似于:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

在其 Bootstrap 方法表

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

: href="https://github.com/openjdk/jdk/blob/827e5e32264666639d36990edd5e7d0b7e7c78a9/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java#L338" rel="noreferrer">引导 方法bootstrap 位于 java.lang.runtime.ObjectMethods 类中。如您所见,此引导方法需要以下参数:

  • 表示查找上下文的 MethodHandles.Lookup 实例
    Ljava/lang/invoke/MethodHandles$Lookup 部分)。
  • 引导程序的方法名称(即toStringequalshashCode等)
    即将链接。例如,当值为toString时,bootstrap
    将返回一个 ConstantCallSite (一个永远不会改变的 CallSite
    指向此特定的实际 toString 实现
    记录。
  • 方法的 TypeDescriptor (Ljava/lang/invoke/TypeDescriptor
    部分)。
  • 类型标记,即Class,表示Record 类类型。它是
    在这种情况下,Class
  • 所有组件名称的分号分隔列表,即 min;max
  • 每个组件一个 MethodHandle。这样bootstrap方法就可以
    根据此特定组件创建一个 MethodHandle
    方法实施。

invokedynamic 指令将所有这些参数传递给引导方法。 Bootstrap 方法又返回 ConstantCallSite 的实例。此ConstantCallSite 保存对所请求方法实现的引用,例如toString

为什么是印地?


与反射 API 相比,java.lang.invoke API 非常高效,因为 JVM 可以完全看穿所有调用。因此,只要我们尽可能避开慢速路径,JVM可能会应用各种优化!

除了效率方面的争论之外,由于 invokedynamic 方法更可靠且更不易损坏。它的简单性

此外,为 Java Records 生成的字节码与属性的数量无关。因此,字节码更少,启动时间更快。

最后,我们假设新版本的 Java 包含一个新的且更高效的引导方法实现。通过 invokedynamic,我们的应用可以利用这一改进而无需重新编译。这样我们就有了某种向前二进制兼容性。另外,这就是我们正在讨论的动态策略!

其他示例


除了 Java 记录之外,动态调用 还用于实现以下功能:

As part of my Java Records article, I articulated about the motivation behind Invoke Dynamic. Let's start with a rough definition of Indy.

Introducing Indy


Invoke Dynamic (Also known as Indy) was part of JSR 292 intending to enhance the JVM support for Dynamic Type Languages. After its first release in Java 7, The invokedynamic opcode along with its java.lang.invoke luggage is used quite extensively by dynamic JVM-based languages like JRuby.

Although indy specifically designed to enhance the dynamic language support, it offers much more than that. As a matter of fact, it’s suitable to use wherever a language designer needs any form of dynamicity, from dynamic type acrobatics to dynamic strategies!

For instance, the Java 8 Lambda Expressions are actually implemented using invokedynamic, even though Java is a statically typed language!

User-Definable Bytecode


For quite some time JVM did support four method invocation types: invokestatic to call static methods, invokeinterface to call interface methods, invokespecial to call constructors, super() or private methods and invokevirtual to call instance methods.

Despite their differences, these invocation types share one common trait: we can’t enrich them with our own logic. On the contrary, invokedynamic enables us to Bootstrap the invocation process in any way we want. Then the JVM takes care of calling the Bootstrapped Method directly.

How Does Indy Work?


The first time JVM sees an invokedynamic instruction, it calls a special static method called Bootstrap Method. The bootstrap method is a piece of Java code that we’ve written to prepare the actual to-be-invoked logic:

enter image description here

Then the bootstrap method returns an instance of java.lang.invoke.CallSite. This CallSite holds a reference to the actual method, i.e. MethodHandle.

From now on, every time JVM sees this invokedynamic instruction again, it skips the Slow Path and directly calls the underlying executable. The JVM continues to skip the slow path unless something changes.

Example: Java 14 Records


Java 14 Records are providing a nice compact syntax to declare classes that are supposed to be dumb data holders.

Considering this simple record:

public record Range(int min, int max) {}

The bytecode for this example would be something like:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

In its Bootstrap Method Table:

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

So the bootstrap method for Records is called bootstrap which resides in the java.lang.runtime.ObjectMethods class. As you can see, this bootstrap method expects the following parameters:

  • An instance of MethodHandles.Lookup representing the lookup context
    (The Ljava/lang/invoke/MethodHandles$Lookup part).
  • The method name (i.e. toString, equals, hashCode, etc.) the bootstrap
    is going to link. For example, when the value is toString, bootstrap
    will return a ConstantCallSite (a CallSite that never changes) that
    points to the actual toString implementation for this particular
    Record.
  • The TypeDescriptor for the method (Ljava/lang/invoke/TypeDescriptor
    part).
  • A type token, i.e. Class<?>, representing the Record class type. It’s
    Class<Range> in this case.
  • A semi-colon separated list of all component names, i.e. min;max.
  • One MethodHandle per component. This way the bootstrap method can
    create a MethodHandle based on the components for this particular
    method implementation.

The invokedynamic instruction passes all those arguments to the bootstrap method. Bootstrap method, in turn, returns an instance of ConstantCallSite. This ConstantCallSite is holding a reference to requested method implementation, e.g. toString.

Why Indy?


As opposed to the Reflection APIs, the java.lang.invoke API is quite efficient since the JVM can completely see through all invocations. Therefore, JVM may apply all sorts of optimizations as long as we avoid the slow path as much as possible!

In addition to the efficiency argument, the invokedynamic approach is more reliable and less brittle because of its simplicity.

Moreover, the generated bytecode for Java Records is independent of the number of properties. So, less bytecode and faster startup time.

Finally, let’s suppose a new version of Java includes a new and more efficient bootstrap method implementation. With invokedynamic, our app can take advantage of this improvement without recompilation. This way we have some sort of Forward Binary Compatibility. Also, That’s the dynamic strategy we were talking about!

Other Examples


In addition to Java Records, the invoke dynamic has been used to implement features like:

一梦等七年七年为一梦 2024-11-26 10:01:11

前段时间,C# 添加了一个很酷的功能,C# 中的动态语法,

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

可以将其视为反射方法调用的语法糖。它可以有非常有趣的应用。请参阅http://www.infoq.com/presentations/Statically-Dynamic- Typing-Neal-Gafter

Neal Gafter,负责 C# 的动态类型,刚刚从 SUN 叛逃到 MS。因此,认为 SUN 内部也讨论过同样的事情并非没有道理。

我记得不久之后,一些 Java 家伙宣布了类似的东西,

InvokeDynamic duck = obj;
duck.quack(); 

不幸的是,这个功能在 Java 7 中找不到。非常失望。对于 Java 程序员来说,他们没有简单的方法在程序中利用 invokedynamic

Some time ago, C# added a cool feature, dynamic syntax within C#

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Think of it as syntax sugar for reflective method calls. It can have very interesting applications. see http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter, who's responsible for C#'s dynamic type, just defected from SUN to MS. So it's not unreasonable to think that the same things had been discussed inside SUN.

I remember soon after that, some Java dude announced something similar

InvokeDynamic duck = obj;
duck.quack(); 

Unfortunately, the feature is no where to be found in Java 7. Very disappointed. For Java programmers, they have no easy way to take advantage of invokedynamic in their programs.

圈圈圆圆圈圈 2024-11-26 10:01:11

在继续调用dynamic之前,有两个概念需要理解。

1.静态与动态类型

静态 - 在编译时执行类型检查(例如 Java)

动态 - 在运行时执行类型检查(例如 JavaScript)

类型检查是一种验证程序类型安全的过程,即检查类和实例变量、方法参数、返回值和其他变量的类型信息。
例如,Java 在编译时就知道 int、String 等,而 JavaScript 中对象的类型只能在运行时确定

2。强类型与弱类型

- 指定对其操作提供的值类型的限制(例如 Java)

- 转换(强制转换)操作的参数如果这些参数具有不兼容的类型(例如 Visual Basic)

知道 Java 是静态弱类型,如何在 JVM 上实现动态强类型语言?

invokedynamic 实现了一个运行时系统,可以在程序编译后选择最合适的方法或函数实现。

示例:
拥有 (a + b) 并且在编译时不知道有关变量 a,b 的任何信息,invokedynamic 会在运行时将此操作映射到 Java 中最合适的方法。例如,如果结果 a,b 是字符串,则调用 method(String a, String b)。如果结果 a,b 是 int,则调用 method(int a, int b)。

invokedynamic 是随 Java 7 引入的。

There are two concepts to understand before continuing to invokedynamic.

1. Static vs. Dynamic Typing

Static - preforms type checking at compile time (e.g. Java)

Dynamic - preforms type checking at runtime (e.g. JavaScript)

Type checking is a process of verifying that a program is type safe, this is, checking typed information for class and instance variables, method parameters, return values, and other variables.
E.g. Java knows about int, String,.. at compile time, while type of an object in JavaScript can only be determined at runtime

2. Strong vs. Weak typing

Strong - specifies restrictions on the types of values supplied to its operations (e.g. Java)

Weak - converts (casts) arguments of an operation if those arguments have incompatible types (e.g. Visual Basic)

Knowing that Java is a Statically and Weakly typed, how do you implement Dynamically and Strongly typed languages on the JVM?

The invokedynamic implements a runtime system that can choose the most appropriate implementation of a method or function — after the program has been compiled.

Example:
Having (a + b) and not knowing anything about the variables a,b at compile time, invokedynamic maps this operation to the most appropriate method in Java at runtime. E.g., if it turns out a,b are Strings, then call method(String a, String b). If it turns out a,b are ints, then call method(int a, int b).

invokedynamic was introduced with Java 7.

温柔少女心 2024-11-26 10:01:11

简而言之,invokedynamic 是 JVM 中的一个新操作码,在 JAVA 7 之前并不存在。

就反射而言,在此定义的上下文中:Java 反射是检查或修改运行时行为的过程运行时的类。,但是,我认为需要更多解释。
来自下面的文章

例如,反射早于集合和泛型。作为一个
结果,方法签名由 Reflection 中的 Class[] 表示
API。这可能很麻烦且容易出错,并且受到以下因素的阻碍:
Java 数组语法的冗长本质。情况变得更加复杂
需要手动装箱和拆箱原始类型并解决问题
void 方法的可能性。

方法句柄来救援

而不是强迫程序员去处理
针对这些问题,Java 7 引入了一个新的 API,称为 MethodHandles,
来表示必要的抽象。这个API的核心是
包 java.lang.invoke,尤其是 MethodHandle 类。
这种类型的实例提供了调用方法的能力,并且它们
是可以直接执行的。它们根据它们的动态类型
参数和返回类型,提供与
考虑到它们的动态使用方式,这是可能的。 API 是
invokedynamic 需要,但也可以单独使用,在这种情况下
它可以被认为是反射的现代、安全替代方案。

引用自 使用invokedynamic理解Java方法调用

这四个是标准形式的字节码表示
Java 8 和 Java 9 中使用的方法调用,它们是
invokevirtual、invokespecial、invokeinterface 和 invokestatic

这就提出了一个问题:第五个操作码,invokedynamic
进入画面。简短的回答是,从 Java 9 开始,有
Java 语言中不直接支持 invokedynamic。

事实上,当 Java 7 中将 invokedynamic 添加到运行时时,
javac 编译器在任何情况下都不会发出新的字节码
无论如何。

从 Java 8 开始,invokedynamic 被用作主要实现
提供先进平台功能的机制。最清晰的之一
使用操作码的最简单的例子是
lambda 表达式的实现。

再说一遍,invokedynamic 是一个新的操作码,它允许 JAVA 中的新对象引用类型(Lambda)。

The short answer is invokedynamic is a new opcode in the JVM that didn't exist prior to JAVA 7.

As far as reflection, within the context of this definition: Java Reflection is a process of examining or modifying the run time behavior of a class at run time., however, I believe more explanation is needed.
From the article below:

For example, reflection predates both collections and generics. As a
result, method signatures are represented by Class[] in the Reflection
API. This can be cumbersome and error-prone, and it is hampered by the
verbose nature of Java’s array syntax. It is further complicated by
the need to manually box and unbox primitive types and to work around
the possibility of void methods.

Method handles to the rescue

Instead of forcing the programmer to deal
with these issues, Java 7 introduced a new API, called MethodHandles,
to represent the necessary abstractions. The core of this API is the
package java.lang.invoke and especially the class MethodHandle.
Instances of this type provide the ability to call a method, and they
are directly executable. They are dynamically typed according to their
parameter and return types, which provides as much type safety as
possible, given the dynamic way in which they are used. The API is
needed for invokedynamic, but it can also be used alone, in which case
it can be considered a modern, safe alternative to reflection.

Quoting from Understanding Java method invocation with invokedynamic

These four are the bytecode representations of the standard forms of
method invocation used in Java 8 and Java 9, and they are
invokevirtual, invokespecial, invokeinterface, and invokestatic.

This raises the question of how the fifth opcode, invokedynamic,
enters the picture. The short answer is that, as of Java 9, there was
no direct support for invokedynamic in the Java language.

In fact, when invokedynamic was added to the runtime in Java 7, the
javac compiler would not emit the new bytecode under any circumstances
whatsoever.

As of Java 8, invokedynamic is used as a primary implementation
mechanism to provide advanced platform features. One of the clearest
and simplest examples of this use of the opcode is in the
implementation of lambda expressions.

So again, invokedynamic is a new opcode that allows for a new object reference type in JAVA, a Lambda.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文