Invokedynamic 101

OracleのJava7リリースでinvokedynamicは、Java仮想マシン(JVM)に新しいバイトコード命令が導入さjava.lang.invokeれ、標準クラスライブラリに新しいAPIパッケージが導入されました。この投稿では、この命令とAPIを紹介します。

invokedynamicの内容と方法

Q:とは何invokedynamicですか?

A: invokedynamic動的メソッド呼び出しを通じて(JVM用の)動的言語の実装を容易にするバイトコード命令です。この命令は、JVM仕様のJava SE 7Editionで説明されています。

動的および静的言語

動的言語(としても知られている動的に型付けされた言語)は、そのタイプチェック通常実行時に実行される高レベルのプログラミング言語として知られている機能である動的型付け。型チェックは、プログラムが型安全であることを確認します。すべての操作引数は正しい型を持っています。 Groovy、Ruby、JavaScriptは動的言語の例です。 (@groovy.transform.TypeChecked注釈により、Groovyはコンパイル時に型チェックを行います。)

対照的に、静的言語静的型付け言語とも呼ばれます)は、コンパイル時に型チェックを実行します。これは、静的型付けと呼ばれる機能です。コンパイラは、プログラムが型が正しいことを確認しますが、一部の型チェックをランタイムに延期する場合があります(キャストとcheckcast命令を考えてください)。Javaは静的言語の一例です。Javaコンパイラは、この型情報を使用して、JVMで効率的に実行できる強い型のバイトコードを生成します。

Q:invokedynamic動的言語の実装をどのように促進しますか?

A:動的言語では、タイプチェックは通常実行時に行われます。開発者は適切なタイプを渡す必要があります。そうしないと、ランタイム障害のリスクがあります。多くの場合java.lang.Object、メソッド引数の最も正確な型です。この状況は型チェックを複雑にし、パフォーマンスに影響を与えます。

もう1つの課題は、動的言語は通常、フィールド/メソッドを既存のクラスに追加したり、既存のクラスから削除したりする機能を提供することです。その結果、クラス、メソッド、およびフィールドの解決を実行時に延期する必要があります。また、多くの場合、異なるシグネチャを持つターゲットにメソッド呼び出しを適合させる必要があります。

これらの課題は、従来、アドホックランタイムサポートをJVM上に構築する必要がありました。このサポートには、ラッパー型クラス、動的なシンボル解決を提供するためのハッシュテーブルの使用などが含まれます。バイトコードは、次の4つのメソッド呼び出し命令のいずれかを使用して、メソッド呼び出しの形式でランタイムへのエントリポイントとともに生成されます。

  • invokestaticstaticメソッドを呼び出すために使用されます。
  • invokevirtual呼び出すために使用されるpublicprotected、非static動的ディスパッチを介し方法。
  • invokeinterfaceinvokevirtualメソッドディスパッチがインターフェイスタイプに基づいていることを除いて、と同様です。
  • invokespecialインスタンス初期化メソッド(コンストラクター)、およびprivate現在のクラスのスーパークラスのメソッドとメソッドを呼び出すために使用されます。

このランタイムサポートはパフォーマンスに影響します。生成されたバイトコードは、多くの場合、1つの動的言語メソッド呼び出しに対して複数の実際のJVMメソッド呼び出しを必要とします。リフレクションは広く使用されており、パフォーマンスの低下につながります。また、多くの異なる実行パスにより、JVMのジャストインタイム(JIT)コンパイラーが最適化を適用することは不可能です。

パフォーマンスの低下に対処するために、このinvokedynamic命令はアドホックランタイムサポートを廃止します。代わりに、最初の呼び出しは、ターゲットメソッドを効率的に選択するランタイムロジックを呼び出すことによってブートストラップし、その後の呼び出しは通常、再ブートストラップすることなくターゲットメソッドを呼び出します。

invokedynamicまた、動的に変化する呼び出しサイトのターゲットをサポートすることにより、動的言語の実装者にもメリットがあります。呼び出しサイト、より具体的には、動的呼び出しサイトinvokedynamic命令です。さらに、JVMは内部でをサポートしているためinvokedynamic、この命令はJITコンパイラーによってより適切に最適化できます。

メソッドハンドル

Q:invokedynamicメソッドハンドルと連携して動的なメソッド呼び出しを容易にすることを理解しています。メソッドハンドルとは何ですか?

A:メソッド・ハンドル「引数または戻り値の任意の変換を用いて、基本メソッド、コンストラクタ、フィールド、または類似の低レベル操作に型付けされた、直接実行可能な基準」です。言い換えると、実行可能コード(ターゲット)を指すCスタイルの関数ポインターに似ており、このコードを呼び出すために逆参照することができます。メソッドハンドルは、抽象java.lang.invoke.MethodHandleクラスによって記述されます。

Q:メソッドハンドルの作成と呼び出しの簡単な例を教えてください。

A:リスト1を確認してください。

リスト1. MHD.java(バージョン1)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findStatic(MHD.class, "hello", MethodType.methodType(void.class)); mh.invokeExact(); } static void hello() { System.out.println("hello"); } }

リスト1に、からなる方法ハンドルデモプログラム記述main()hello()クラスメソッドを。このプログラムの目標はhello()、メソッドハンドルを介して呼び出すことです。

main()の最初のタスクは、java.lang.invoke.MethodHandles.Lookupオブジェクトを取得することです。このオブジェクトは、メソッドハンドルを作成するためのファクトリであり、仮想メソッド、静的メソッド、特殊メソッド、コンストラクター、フィールドアクセサーなどのターゲットを検索するために使用されます。さらに、呼び出しサイトの呼び出しコンテキストに依存し、メソッドハンドルが作成されるたびにメソッドハンドルのアクセス制限を適用します。つまり、main()ルックアップオブジェクトを取得する呼び出しサイト(リスト1の呼び出しサイトとして機能するメソッドなど)は、呼び出しサイトにアクセスできるターゲットにのみアクセスできます。ルックアップオブジェクトは、java.lang.invoke.MethodHandlesクラスのMethodHandles.Lookup lookup()メソッドを呼び出すことによって取得されます。

publicLookup()

MethodHandlesまた、MethodHandles.Lookup publicLookup()メソッドを宣言します。lookup()アクセス可能なメソッド/コンストラクターまたはフィールドへのメソッドハンドルを取得するために使用できるとは異なり、publicLookup()パブリックにアクセス可能なフィールドまたはパブリックにアクセス可能なメソッド/コンストラクターのみへのメソッドハンドルを取得するために使用できます。

ルックアップオブジェクトを取得した後、このオブジェクトのMethodHandle findStatic(Class refc, String name, MethodType type)メソッドを呼び出して、メソッドへのhello()メソッドハンドルを取得します。渡される最初の引数は、メソッド()にアクセスfindStatic()するクラス(MHD)への参照hello()であり、2番目の引数はメソッドの名前です。 3番目の引数は、メソッドタイプの例です。これは、「メソッドハンドルによって受け入れられて返される引数と戻り値の型、またはメソッドハンドルの呼び出し元によって渡され期待される引数と戻り値の型を表します」。これはjava.lang.invoke.MethodTypeクラスのインスタンスによって表され、(この例では)java.lang.invoke.MethodTypeMethodType methodType(Class rtype)メソッドを呼び出すことによって取得されます。このメソッドが呼び出されるのhello()は、戻り値の型のみを提供するためです。void。この戻り値の型は、このメソッドにmethodType()渡すvoid.classことで使用できるようになります。

返されたメソッドハンドルはに割り当てられmhます。次に、このオブジェクトを使用してMethodHandleObject invokeExact(Object... args)メソッドを呼び出し、メソッドハンドルを呼び出します。つまり、invokeExact()結果hello()として呼び出さhelloれ、標準出力ストリームに書き込まれます。のでinvokeExact()ISがスローするように宣言しThrowable、私が追加しましたthrows Throwablemain()方法ヘッダー。

Q:以前の回答で、ルックアップオブジェクトは呼び出しサイトにアクセスできるターゲットにのみアクセスできるとおっしゃいました。アクセスできないターゲットへのメソッドハンドルを取得しようとしていることを示す例を提供できますか?

A:リスト2を確認してください。

リスト2. MHD.java(バージョン2)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class HW { public void hello1() { System.out.println("hello from hello1"); } private void hello2() { System.out.println("hello from hello2"); } } public class MHD { public static void main(String[] args) throws Throwable { HW hw = new HW(); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findVirtual(HW.class, "hello1", MethodType.methodType(void.class)); mh.invoke(hw); mh = lookup.findVirtual(HW.class, "hello2", MethodType.methodType(void.class)); } }

リスト2はHW(Hello、World)とMHDクラスを宣言しています。インスタンスメソッドとインスタンスメソッドをHW宣言しpublichello1()ますprivatehello2()。これらのメソッドを呼び出そうとMHDするmain()メソッドを宣言します。

main()「最初のタスクは、インスタンス化することですねHW呼び出すための準備hello1()hello2()。次に、ルックアップオブジェクトを取得し、このオブジェクトを使用して、を呼び出すためのメソッドハンドルを取得しますhello1()。今回は、MethodHandles.LookupfindVirtual()メソッドが呼び出され、このメソッドに渡される最初の引数はClassHWクラスを記述するオブジェクトです。

それがfindVirtual()成功し、後続のmh.invoke(hw);式がを呼び出してhello1()hello from hello1出力されることがわかります。

Because hello1() is public, it's accessible to the main() method call site. In contrast, hello2() isn't accessible. As a result, the second findVirtual() invocation will fail with an IllegalAccessException.

When you run this application, you should observe the following output:

hello from hello1 Exception in thread "main" java.lang.IllegalAccessException: member is private: HW.hello2()void, from MHD at java.lang.invoke.MemberName.makeAccessException(MemberName.java:507) at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1172) at java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:1152) at java.lang.invoke.MethodHandles$Lookup.accessVirtual(MethodHandles.java:648) at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:641) at MHD.main(MHD.java:27)

Q: Listings 1 and 2 use the invokeExact() and invoke() methods to execute a method handle. What's the difference between these methods?

A: Although invokeExact() and invoke() are designed to execute a method handle (actually, the target code to which the method handle refers), they differ when it comes to performing type conversions on arguments and the return value. invokeExact() doesn't perform automatic compatible-type conversion on arguments. Its arguments (or argument expressions) must be an exact type match to the method signature, with each argument provided separately, or all arguments provided together as an array. invoke() requires its arguments (or argument expressions) to be a type-compatible match to the method signature -- automatic type conversions are performed, with each argument provided separately, or all arguments provided together as an array.

Q: Can you provide me with an example that shows how to invoke an instance field's getter and setter?

A: Check out Listing 3.

Listing 3. MHD.java (version 3)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class Point { int x; int y; } public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); Point point = new Point(); // Set the x and y fields. MethodHandle mh = lookup.findSetter(Point.class, "x", int.class); mh.invoke(point, 15); mh = lookup.findSetter(Point.class, "y", int.class); mh.invoke(point, 30); mh = lookup.findGetter(Point.class, "x", int.class); int x = (int) mh.invoke(point); System.out.printf("x = %d%n", x); mh = lookup.findGetter(Point.class, "y", int.class); int y = (int) mh.invoke(point); System.out.printf("y = %d%n", y); } }

Listing 3 introduces a Point class with a pair of 32-bit integer instance fields named x and y. Each field's setter and getter is accessed by calling MethodHandles.Lookup's findSetter() and findGetter() methods, and the resulting MethodHandle is returned. Each of findSetter() and findGetter() requires a Class argument that identifies the field's class, the field's name, and a Class object that identifies the field's signature.

The invoke() method is used to execute a setter or getter-- behind the scenes, the instance fields are accessed via the JVM's putfield and getfield instructions. This method requires that a reference to the object whose field is being accessed be passed as the initial argument. For setter invocations, a second argument, consisting of the value being assigned to the field, also must be passed.

When you run this application, you should observe the following output:

x = 15 y = 30

Q: Your definition of method handle includes the phrase "with optional transformations of arguments or return values". Can you provide an example of argument transformation?

A:Mathクラスのdouble pow(double a, double b)classメソッドに基づいて例を作成しました。この例では、メソッドへのメソッドハンドルを取得し、pow()渡される2番目の引数pow()が常にになるようにこのメソッドハンドルを変換し10ます。リスト4をチェックしてください。