invokedynamicとは何ですか?どのように使用しますか?


159

JVMに追加されているすべての新しいクールな機能について聞き続けていますが、それらのクールな機能の1つはinvokedynamicです。私はそれが何であるか、そしてそれがどのようにJavaでのリフレクションプログラミングをより簡単またはより良くするのか知りたいのですか?

回答:


165

これは新しいJVM命令で、コンパイラーは以前よりも緩い仕様でメソッドを呼び出すコードを生成できます。「ダックタイピング」とは何かを知っている場合、invokedynamicは基本的にダックタイピングを可能にします。Javaプログラマーができることはそれほど多くありません。ただし、ツール作成者であれば、それを使用して、より柔軟で効率的なJVMベースの言語を構築できます。ここにたくさんの詳細を与える本当に甘いブログ投稿があります。


3
日常のJavaプログラミングでは、を使用してメソッドを動的に呼び出すためにリフレクションが使用されることは珍しくありませんmeth.invoke(args)。では、どのようにinvokedynamic適合しmeth.invokeますか?
デビッドK.

1
私が言及しているブログ投稿では、について話していますがMethodHandle、これは本当に同じようなことですが、柔軟性がはるかに高くなっています。しかし、これらすべての真の力は、Java言語に加えてではなく、本質的に動的な他の言語をサポートするJVM自体の機能にあります。
アーネストフリードマンヒル


1
Java 8は、ラムダのいくつかを使用しinvokedynamicて翻訳し、パフォーマンスを向上させているようです(導入する前にほとんど唯一の選択肢であった匿名の内部クラスでそれらをラップする場合と比較してinvokedynamic)。ほとんどの場合、JVM上の多くの関数型プログラミング言語は、内部クラスではなく、これにコンパイルすることを選択します。
Nader Ghanbari、2015

2
ちょっとした警告ですが、2008年のブログ投稿は絶望的に古く、実際のリリース状態(2011)を反映していません。
Holger

9

少し前に、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を参照してください

C#の動的型を担当するNeal Gafterは、SUNからMSに移籍しました。したがって、同じことがSUNの内部で議論されたと考えることは不合理ではありません。

その後すぐに覚えています

InvokeDynamic duck = obj;
duck.quack(); 

残念ながら、この機能はJava 7にはありません。非常にがっかりしました。Javaプログラマーにとって、彼らはプログラムで活用する簡単な方法はありませんinvokedynamic


41
invokedynamicJavaプログラマーが使用することを意図したものではありません。IMOは、Javaの哲学にまったく適合しません。これは、Java以外の言語のJVM機能として追加されました。
Mark Peters

5
@マーク誰が意図したことはありませんか?Java言語の有名人に明確な権力構造があるのではなく、明確に定義された集合的な「意図」があるのとは異なります。言語哲学については-かなり実現可能です。ニールガフター(裏切り者!)の説明を参照してください:infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
評判の悪い

3
@mark peters:invokedynamicは、実際には直接アクセスできないJavaプログラマーも対象としています。これは、Java 8のクロージャーの基礎です。
M Platvoet 2011

2
@評判が悪い:JSR寄稿者が意図したものではありません。これは、JSRの名前が「Javaプラットフォームでの動的型付け言語のサポート」であることを示しています。Javaは動的に型付けされた言語ではありません。
Mark Peters

5
@M Platvoet:私は最新のクロージャーを利用していませんが、クロージャーの絶対的な要件ではありません。彼らが議論した別のオプションは、VM仕様を変更せずに実行できる匿名の内部クラスのクロージャ構文糖を単に作ることでした。しかし、私の主張は、JSRが動的型付けをJava言語にもたらすことを意図したものではなかったということです。JSRを読めばそれは明らかです。
Mark Peters

4

invokedynamicを続ける前に理解しておくべき2つの概念があります。

1.静的型とダイナミン型

Static-コンパイル時に型チェックをプリフォームします(例: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で導入されました。


4

Java Recordsの記事の一部として、私はInoke Dynamicの背後にある動機について明確に述べました。インディの大まかな定義から始めましょう。

インディの紹介

Invoke Dynamic(別名Indy)は、動的タイプ言語のJVMサポートを強化することを目的としたJSR 292の一部でした。Java 7での最初のリリース後、invokedynamicオペコードとそのjava.lang.invoke荷物は、JRubyなどの動的なJVMベースの言語で非常に広く使用されています。

動的言語サポートを強化するために特別に設計されたindyですが、それだけではありません。実際のところ、言語設計者が動的型アクロバットから動的戦略まで、あらゆる形式の動的性を必要とするところはどこでも使用するのに適しています!

たとえば、Javaは静的に型付けされた言語ですが、Java 8のラムダ式は実際にはを使用invokedynamicして実装されています。

ユーザー定義可能なバイトコード

かなり前から、JVMは4つのメソッド呼び出しタイプ(invokestatic静的メソッドのinvokeinterface呼び出し、インターフェースメソッドのinvokespecial呼び出し、コンストラクターの呼び出し、super()プライベートメソッドのinvokevirtual呼び出し、およびインスタンスメソッドの呼び出し)をサポートしていました。

それらの違いにもかかわらず、これらの呼び出しタイプは1つの共通の特性を共有しますつまり、独自のロジックでそれらを強化することはできません。それどころか、invokedynamic 私たちは好きな方法で呼び出しプロセスをブートストラップすることができます。次に、JVMがBootstrappedメソッドを直接呼び出します。

インディはどのように機能しますか?

JVMが初めてinvokedynamic命令を見ると、Bootstrapメソッドと呼ばれる特別な静的メソッドを呼び出します。ブートストラップメソッドは、実際に呼び出されるロジックを準備するために作成したJavaコードの一部です。

ここに画像の説明を入力してください

次に、ブートストラップメソッドはのインスタンスを返しますjava.lang.invoke.CallSite。これCallSiteは、実際のメソッドへの参照を保持しますMethodHandle

これからは、JVMはこの見ているたびにinvokedynamic、再び命令は、それがスキップ低速パスを直接根底にある実行ファイルを呼び出します。何かが変更されない限り、JVMは低速パスをスキップし続けます。

例:Java 14レコード

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

その中にブートストラップ法表

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

したがって、クラス内にあるRecords のブートストラップメソッドが呼び出さbootstrapれますjava.lang.runtime.ObjectMethods。ご覧のとおり、このブートストラップメソッドは次のパラメーターを想定しています。

  • MethodHandles.Lookupルックアップコンテキスト(Ljava/lang/invoke/MethodHandles$Lookupパーツ)を表すインスタンス。
  • メソッド名(すなわちtoStringequalshashCode、など)、ブートストラップは、リンクに起こっています。たとえば、値がのtoString場合、ブートストラップは、この特定のレコードの実際の実装を指すConstantCallSiteCallSite変更されない)を返しtoStringます。
  • TypeDescriptor方法(のためのLjava/lang/invoke/TypeDescriptor 部分)。
  • タイプトークン。つまりClass<?>、Recordクラスタイプを表します。それはだ Class<Range>、この場合には。
  • すべてのコンポーネント名のセミコロンで区切られたリスト、すなわちmin;max
  • 一つのMethodHandleコンポーネントごと。このようにして、ブートストラップメソッドはMethodHandle、この特定のメソッド実装のコンポーネントに基づいてを作成できます。

このinvokedynamic命令は、これらすべての引数をブートストラップメソッドに渡します。次に、Bootstrapメソッドはのインスタンスを返しますConstantCallSite。これConstantCallSiteは、要求されたメソッド実装への参照を保持していますtoString

なぜインディなのか?

Reflection APIとは対照java.lang.invoke的に、JVMはすべての呼び出しを完全に確認できるため、APIは非常に効率的です。したがって、低速パスを可能な限り回避する限り、JVMはあらゆる種類の最適化を適用する可能性があります。

効率の議論に加えて、このinvokedynamicアプローチは、その単純さのためにより信頼性が高く、もろくなりません。

さらに、Javaレコード用に生成されたバイトコードは、プロパティの数に依存しません。したがって、バイトコードが少なくなり、起動時間が速くなります。

最後に、新しいバージョンのJavaに、新しく効率的なブートストラップメソッドの実装が含まれているとします。を使用するinvokedynamicと、アプリはこの改善を再コンパイルなしで利用できます。このように、ある種の前方バイナリ互換性があります。また、それは私たちが話していた動的な戦略です!

その他の例

Javaレコードに加えて、動的呼び出しは次のような機能を実装するために使用されています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.