JDK 7の新機能:Java仮想マシンにおける動的型付け言語のサポート

Ed Ort著、2009年7月

Ed OrtライターのEd OrtEd Ortは、Sun Developer Networkのスタッフです。 リレーショナル・データベース・テクノロジー、プログラミング言語、Webサービス、Ajaxなど、多岐にわたるプログラミング・トピックについて広範に扱っています。 彼のブログをご覧ください。

DaVinci Helicopterこの記事では、JDK 7の新機能である、 Java仮想マシン(JVM)における動的型付け言語のサポートについて説明します。 この機能は、JSR 292: Supporting Dynamically Typed Languages on the Java Platform(Javaプラットフォームにおける動的型付け言語のサポート)を実装したものであり、JSR 223: Scripting for the Java Platform(Javaプラットフォーム向けスクリプト)を論理的に引き継いでいます。 JSR 223のサポートはJava SE 6に含まれており、JDK 6で実装されています。

JDK 7ではJSR 292のサポートの追加によって、動的型付け言語がJVMでこれまでよりも高速に動作するようになります。 このサポートで重要なのは、メソッド呼出し用のinvokedynamicという新しいJavaバイトコードと、それに付随して、メソッド・ハンドルと呼ばれる新しい構成要素を伴うリンケージ・メカニズムが追加されたことです。 これらの機能を使用すると、動的型付け言語のコンパイラの実装者、つまり、JRubyやJythonといった言語のコンパイラの開発者が、JVMで非常に高速に動作するバイトコードを生成できるようになります。

これによって、JVMで動作する動的型付け言語の多様性と品質の向上が見込まれます。 アプリケーション開発者にとっては、Javaエコシステムで利用できる好みの動的型付け言語が増えるはずです。 また、これらの機能を使用すると、すでにJVMで動作している動的型付け言語のコンパイラによって生成されるコードのパフォーマンスも向上すると見込まれます。 たとえば、JRubyコンパイラはJVMでのパフォーマンスに優れたバイトコードを生成しますが、JRubyコンパイラがinvokedynamicバイトコードとメソッド・ハンドルを使用するよう改修されれば、JRubyバイトコードはさらに高速に動作するようになります。

また、JSR 292の専門家グループは、インタフェース・インジェクションのサポートについても検討中です。これは、クラスが新しいインタフェースを実装できるように、実行時にクラスを変更できるものであり、動的型付け言語では一般的な機能です。

JDK 7でのJSR 292のサポートによって、動的型付け言語のコンパイラ開発者は、JVMで極めて高速に動作するバイトコードを生成できます。

JVMで動作する新しい言語の追加を促進する他の要因として、Da Vinci Machine ProjectOpenJDKコミュニティの1つ)の活動があります。 このプロジェクトのミッションは、Java以外の言語、特に動的言語をサポートするようJVMを拡張することです。 プロジェクトは数々のサブプロジェクトを担っています。 その1つに、invokedynamicバイトコードの実装があります。 その他のDa Vinci Machineサブプロジェクトには、インタフェース・インジェクションおよびMethodHandleのサポートの実装があります。 Da Vinci Machineサブプロジェクトで開発されるその他のJVMの拡張は、将来のJDKバージョンに組み込まれる可能性があります。

目次

動的型付け言語とJVM

開発者にJVMとは何かを問いかければ、おそらく「マシン非依存のバイトコードに変換されたJavaプログラムを実行するプログラムである」という回答が返ってくるでしょう。 この回答は正しいのですが、完全ではありません。 JVMは確かに、バイトコードに変換されたJavaプログラムを実行します。 実際、マシン非依存のバイトコードを実行するという理由で、JVMはJavaプラットフォームの土台となっています。 JVMのバイブルであるThe Java Virtual Machine Specification(Java仮想マシン仕様)では、JVMの重要性として次の点が強調されています。

JVMは、ハードウェア・システム/オペレーティング・システム非依存であり、コンパイル済みコードのサイズが小さく、悪意のあるプログラムからユーザーを保護できるという責務を持つテクノロジーのコンポーネントです。

しかし、JVMの機能は、変換済みJavaプログラムの処理だけにとどまりません。 Java仮想マシン仕様には次の記述もあります。

Java仮想マシンはJavaプログラミング言語については一切理解せず、特定のバイナリ形式であるclassファイル形式のみを理解します。 classファイルには、Java仮想マシンの命令(すなわちバイトコード)とシンボル・テーブル、その他の補助情報が含まれます。 Java仮想マシンではセキュリティの目的で、classファイル内のコードに対して、形式と構造に関する強力な制約が課せられます。 ただし、有効なclassファイルとして表現可能な機能を持つ言語はすべて、Java仮想マシンでホストできます。 マシン非依存の汎用プラットフォームという魅力があり、他の言語の実装者はJava仮想マシンを、独自の言語の配信媒体として使用し始めています。
JVMがホストする言語は増加しています。 ますます多くの動的言語のJVM実装が利用できるようになっています。

JVMは年月を重ねるにつれて、Armed Bear Common Lisp(Common LISP言語の実装)からYoix(汎用スクリプト言語)に至るまで、より多くの言語をホストしています。 ますます多くの動的言語のJVM実装が利用できるようになっており、例としては、JRuby(Rubyプログラミング言語の実装)、Jython(Pythonプログラミング言語の実装)、Groovyスクリプト言語などがあります。

多くのアプリケーション開発者にとって、JVMにホストされる動的言語が増加することは嬉しいニュースです。 動的言語、特にスクリプト言語に備わる柔軟性は、アプリケーションのプロトタイプ作成や試験において、さらには、急速に進化するアプリケーションにとって特に魅力的です。 この柔軟性は動的型付けによって得られます。 動的に型付けされた言語は、アプリケーション内の値が期待する型に一致することを実行時に検証します。 これと比較して、Javaプログラミング言語などの静的型付け言語では、型チェックの大部分をコンパイル時に実行します。この際にチェックするのは変数の型であり、値ではありません。 Java言語では、一部で値の動的型チェックも行うことができます。これは特に、仮想メソッド・コールやインタフェース・メソッド・コールのレシーバに当てはまります。 しかし、このようなコールでも、レシーバの静的な型情報が必要になります。

動的型付けは多くの場合、プログラムが実行時のデータに基づいて型の生成や設定を行えるという点で、静的型付けよりも柔軟性に優れています。 さらに、動的型付け言語では型一致ルールがより寛大であり、多くの型変換を自動的に実行できます。 動的型付けにはこれらの特徴があることから、静的型付け言語でコーディングするよりも迅速にアプリケーションを作成できる傾向にあります。 しかし、多くの場合、静的型付け言語で記述したプログラムの方がより効率的に実行されます。 さらに静的型付けでは、コンパイル時に多くのエラーを除去できます。 静的型付けの欠点は、正常に実行できる可能性のあるプログラムがコンパイル時に拒否されることがあるというところにあります。

このため、動的型付けに備わる柔軟性をJVMの実行効率と組み合わせます。この効率は、長年の経験を積む多くの先進的なエンジニアの貢献によって培われたものです。動的プログラミング言語の開発者や、さらにはこれらの言語を使用してアプリケーションを作成する開発者にとって、動的型付け言語をJVMでサポートすることが魅力的である理由を容易に理解できます。

JSR 223 - 動的言語サポートの最初のステップ

JVMに動的言語を導入する最初のステップはJSR 223: Scripting for the Java Platformで実施されました。JSR 223は、動的スクリプト言語で記述されたコードからJavaコードにアクセスするためのAPIを定義する仕様です。 JSR 223では、Javaアプリケーションにおけるスクリプト・エンジンをホストするためのフレームワークについても規定しています。 スクリプト・エンジンとは、スクリプト・コードをコンパイルするか、またはインタプリタで解釈して、その後実行するプログラムを指します。 この仕様と実装によって、Javaコードとスクリプト・コードの両方を含むアプリケーションの作成が格段に容易になりました。

JSR 223はJava SE 6に含まれ、JDK 6で実装されています。さらに、JDK 6にはRhinoスクリプト・エンジンも含まれています。これは、JavaScriptスクリプト言語の実装です。 また、Sunはスクリプト言語プロジェクトも開始しました。このプロジェクトのおもな目標は、Javaアプリケーションで使用する追加のスクリプト・エンジンを構築するためのコミュニティを促進することです。 すでに20以上のスクリプト・エンジンがこのプロジェクトで作成され、そのすべてがJVMで実行できます。

動的型付け言語の問題

JVMの要件への対応は、メソッド呼出し用のバイトコードを生成する際に、動的言語の実装者が苦心してきたところです。

JSR 223のサポートによって、JVM向けのスクリプト・エンジンの開発が促進されましたが、これらのスクリプト・エンジンの開発者は厄介な障壁に直面しています。 開発者がJVMで動作する動的型付け言語のエンジンを記述する際には、JVMが実行するJavaバイトコードの要件を満たす必要があります。 これまで、そのバイトコードは静的型付け言語に特化して設計されていました。 このような設計のため、スクリプト・エンジンの開発者はメソッド呼出し用のバイトコードを生成する際に苦心しています。

メソッド呼出しのバイトコード要件

繰り返しになりますが、静的型付け言語はコンパイル時に型チェックを行います。 このため、メソッド呼出しでは、コンパイラ、およびコンパイラが生成するバイトコードが、メソッドの戻り値の型や、コールに指定されたレシーバやパラメータの型を知っている必要があるということになります。

次のJavaコードについて考えてみましょう。

   String s = "Hello World";
   System.out.println(s);

メソッドのパラメータの型が既知であることに注意してください。 Stringです。 println()メソッドには戻り値はありませんが、このコード例で戻り値のあるメソッドがコールされるのであれば、その戻り値の型を指定する必要があります。 コールのレシーバであるSystem.outにも、静的な既知の型があります。この例ではjava.io.PrintStreamです。

必要な型情報を備えた状態で、javacコンパイラは次のような適切なバイトコード命令を生成できます。

   ldc #2
   astore_1
   getstatic #3
   aload_1
   invokevirtual #4

バイトコードになじみがない場合は、次の重要な点を理解しておいてください。それは、JVMでのバイトコード実行の多くで、オペランド・スタック内の値に対する処理が行われるということです。 オペランド・スタックとは、仮想マシンにおける、実際のマシンでのハードウェア・レジスタに相当するものです。 バイトコードの大部分で、ローカル値の値をオペランド・スタックにプッシュしたり、スタックから値をポップしてローカル変数に格納したり、スタック内の値を複製したり、スタック内の値をスワップしたり、値の生成や使用を行う処理を実行したりします。

例として、同じバイトコードにコメントを付加したバージョンを次に示します。

   ldc #2  // Push the String "Hello World" onto the stack
   astore_1 // Pop the value "Hello World" from the stack and store it in local variable #1
   getstatic #3 // Get the static field java/lang/System.out:Ljava/io/PrintStream from the class
   aload_1 // Load the String referenced from local variable #1
   invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

この例で、invokevirtualバイトコード命令は、その他のバイトコード命令とは明らかに異なります。 この命令はオペランド・スタックを操作せず、メソッドを呼び出しています。 invokevirtualバイトコード命令に対するコメントも、一風変わったものに見えるかもしれません。 このコメントが表す内容は次のとおりです。

  • メソッドを提供するレシーバ・クラス:java.io.PrintStream
  • メソッド名:println
  • メソッド引数の型:(Ljava/lang/String;)Stringを表す)
  • メソッドの戻り型:Vvoidを表す)

これらの情報は、メソッド呼出しのシグネチャとなります。 invokevirtualバイトコード命令に対する応答として、JVMは指定されたシグネチャを持つメソッドを検索します。この例の場合、java.io.PrintStreamクラスのprintln:(Ljava/lang/String;)Vです。 メソッドがそのクラスに存在しない場合は、JVMはクラスのスーパークラスの連鎖を上方にたどって検索します。

invokevirtualおよびその他の"invoke"型バイトコード命令の詳細については、JSR 292 - 動的言語サポートの次のステップを参照してください。

要件を満たすための苦しい試み

動的型付け言語では実行時まで型情報が指定されないので、実装者は、メソッド呼出しのバイトコード要件を満たすためにさまざまなアプローチを試す必要がありますが、そのどれも最適なものではありません。 たとえば、仮想の動的型付け言語に次のようなコードがあるとします。

   function max (x,y) {
      if x.lessThan(y) then y else x
   }

このコードには、レシーバや引数の型がまったく指定されていません。前述のとおり、動的型付け言語では実行時までは型情報が指定されないのです。 結果として、このコードでは、事前に型を知っているというメソッド呼出しの要件が満たされていません。 このコードは、Javaプラットフォームでのバイトコードへのコンパイルに失敗します。

この問題を解決する一般的なアプローチの1つは、戻り値とメソッドの引数(特にメソッド・コールのレシーバとして動作するもの)向けに、言語固有のJava型を作成することです。 たとえば、ある動的型付け言語の実装として、前述のコードを次のように変更できます。

   MyObject function max (MyObject x,MyObject y) {
      if x.lessThan(y) then y else x
   }

この言語固有のベース型であるMyObjectには、isLessThanメソッドなどの、メソッド呼出しのバイトコード要件を満たすために動的言語で使用する可能性のあるすべてのメソッドが含まれます。

レシーバの型付けの問題を解決する他の手法として、リフレクション呼出しというものがあります。 このアプローチでは、java.lang.reflect.Methodオブジェクトを使用してメソッドを呼び出します。 Methodオブジェクトを使用してメソッド呼出しを行うことで、直接メソッドを呼び出す必要がなくなります。この結果、戻り値の型やパラメータの型を指定する必要があるという要件を回避できます。

動的型付け言語の実装者は、メソッド呼出しのバイトコード要件を満たすためにさまざまなアプローチを試してきましたが、そのどれも最適なものではありません。

動的言語の実装者が利用する3つ目のアプローチは、メソッド呼出し用の言語固有インタプリタを作成し、JVMの上の層で実行することです。

言語固有のベース型を作成するとJavaバイトコードの要件を満たすことはできますが、事前に指定されたベース型に各レシーバを静的に型付けしなければならないという制約があります。 この手法には、言語の実装者が、ベース型に配置すべき全メソッドのリストを事前に考案しなければならないという問題があります。 エンドユーザーが定義するメソッドなどの将来のメソッドは、invokeapplyなどのような包括的なメソッドを使用して、あまり直接的ではない方法でシミュレートする必要があります。 また、インタフェース・インジェクションが選択肢とならない限り、StringIntegerなどのJVMシステム型を、言語固有メソッド・コールのレシーバとして直接使用できません。

リフレクション呼出しアプローチには、いくつかの独自の制約があります。 たとえば、java.lang.reflect.Methodオブジェクトでは、クラスまたはインタフェース上のメソッドのうち、まさに動的言語の実装者が必要とするものにアクセスできるのですが、そのオブジェクトは実行時に利用できるJava固有の型でなければなりません。 動的言語では実行時に型情報を指定できますが、そのすべてがリフレクションで使用できる通常のJava型とは限らないのです。 これは特に、JRubyやRhinoなどのインタプリタを備える動的言語に当てはまります。

JVMの上の層でインタプリタを実行してメソッド呼出しを処理すると、比較的処理が遅くなります。JVMで直接処理を行うよりも遅くなるのは間違いありません。

JSR 292 - 動的言語サポートの次のステップ

JSR 292では、invokedynamicというJVMの新しいJavaバイトコード命令と、新しいメソッド・リンケージ・メカニズムが導入されています。

JSR 292では、角形の釘を丸い穴に打ち付けるかのように、動的型付け言語におけるメソッド呼出しを静的ベースのJavaバイトコード要件に合わせようとすることで生じる問題を解決することを目標としています。 これは、invokedynamicというJVMの新しいJavaバイトコード命令と、新しいメソッド・リンケージ・メカニズムを導入することで実現しています。


メソッド呼出し用のバイトコード命令

Java仮想マシン仕様はその誕生以来、メソッド呼出し用に次の4つのバイトコードを規定しています。

  • invokevirtual - クラスのメソッドを呼び出します。 これが通常のメソッド呼出しです。
  • invokeinterface - インタフェースのメソッドを呼び出します。
  • invokestatic - クラスの静的メソッドを呼び出します。 これは、レシーバの引数がない唯一の呼出しです。
  • invokespecial - レシーバの型を参照せずにメソッドを呼び出します。 この方法でコールされるメソッドは、コンストラクタ、スーパークラスのメソッド、またはプライベート・メソッドです。

ここでは、次の2つのバイトコードについて確認します。 1つは、もっとも一般的なメソッド呼出しであるinvokevirtual、もう1つは、新しいinvokedynamic命令に形式が似ているinvokeinterfaceです。

invokevirtual命令

invokevirtualバイトコード命令は、前述のバイトコードの例でも取り上げています。

   invokevirtual #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V

命令のinvokevirtual部分は1バイトのオペレーション・コードです。 残りの命令の#4は、2バイトのオペランドです。これは、メソッド・コールに関する情報を抽象的に示すものです。 このオペランドは、定数のプール内のエントリを参照します。 そのエントリには、メソッド呼出しに関連する情報をまとめたシンボリック参照が格納されます。 この情報には、メソッド、メソッド名、メソッド記述子で構成されるレシーバ・クラスが含まれます。 メソッド記述子は、メソッドの戻り型とそれぞれの引数の型を指定するものです。

前述のとおり、メソッド呼出しのレシーバはjava.io.PrintStreamです。 メソッド名はprintlnです。 メソッドの戻り型はvoidであり、これはVで表されています。 さらに、メソッドには1つのString引数があり、これは(Ljava/lang/String;)で表されています。

invokevirtualバイトコード命令の構文は次のとおりです。

   invokevirtual <method-specification>

ここで、<method-specification>は前述の定数プール・インデックスを指します。

invokeinterface命令

Javaプログラムのinvokeinterfaceバイトコード命令は、次のような表記になります。

   invokeinterface #9,  2; //InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z

invokeinterfaceバイトコード命令の構文は次のとおりです。

   invokeinterface <method-specification> <n>

ここで、<method-specification>はインタフェース名、メソッド名、記述子を指定する2バイトの定数プール・インデックスを表し、<n>は引数の数を指定する2バイトのオペランドを表します。

invokevirtualと同様に、記述子はメソッドの引数の型とメソッドの戻り型を指定します。 この例では、<method-specification>は、インタフェースがjava.util.Listであること、およびメソッドがaddであることを指定しています。 型の記述子は、このメソッドがjava.lang.Object型の引数を取り(表記はLjava/lang/Object)、booleanの結果を返す(表記はZコード)ことを指定しています。

この例の<n>値は2です。これは、メソッドがレシーバも含めて2つの引数を取ることを示しています。

invokedynamic命令

新しいinvokedynamicバイトコード命令の構文は、invokeinterface命令の構文と似ています。

   invokedynamic <method-specification> <n>

ただし、invokeinterface命令とは異なり、<method-specification>に指定する必要があるのはメソッド名と記述子だけです。 この場合、2バイトの数値<n>は、ゼロにする必要があります。 一部のJVMではこの余分なバイトを内部的に使用して、命令をランタイム構造にリンクする場合もあります。

invokedynamicバイトコード命令は、次のような表記になります。

   Invokedynamic #10; //NameAndTypelessThan:(Ljava/lang/Object;Ljava/lang/Object;)

重要な点として、invokedynamicバイトコード命令では、動的言語の実装者がメソッドを含むターゲット型を指定せずに、メソッド呼出しをバイトコードに変換できます。 この場合のメソッド仕様では、定数プール参照がシンプルになっており、この参照はメソッド名と型の記述子のみを指定します。 この記述子は、メソッド・コールの戻り型とメソッド引数の型を指定します。 invokestaticと同様に、レシーバとなる引数はありません。

しかし、ちょっと待ってください。 レシーバの型が指定されていないのに、JVMはどうやってメソッドを検索するのでしょうか。 最終的には、JVMは実際の型の実際のメソッドにリンクされ、そのメソッドを呼び出す必要があるはずです。 この疑問に対する答えとして、JSR 292には動的型付け言語向けの新しいリンケージ・メカニズムも含まれています。 JVMはinvokedynamicバイトコードを確認すると、この新しいリンケージ・メカニズムを使用して必要なメソッドに到達します。

新しい動的リンケージ・メカニズム:メソッド・ハンドル

メソッド・ハンドルを使用すれば、JVMはinvokedynamicバイトコード命令に対する応答として正しいメソッドを呼び出すことができます。

動的型付け言語向けのこの新しいリンケージ・メカニズムには、メソッド・ハンドルという新しい構成要素が関係します。 JDK 7にはjava.dynという新しいパッケージが含まれており、このパッケージには、Javaプラットフォームでの動的言語サポートに関連するクラスが格納されています。 このパッケージ内のクラスの1つが、MethodHandleです。 メソッド・ハンドルとは、JVMメソッドに対する匿名参照が含まれた、java.dyn.MethodHandle型のシンプルなオブジェクトです。 メソッド・ハンドルは、メソッドに対する名前付き参照とまったく同じようにコール可能です。 しかし、このクラス特有の機能として、このクラスはポインタ構造を通してアクセスされます。これは、リンクされた名前によるアクセスとは対照的です。

他の呼出し命令と同様に、invokedynamic命令は初回実行時にリンクされます。 このリンクが生じると、個々のinvokedynamic命令に対して、メソッド・ハンドルがそれ専用のターゲットとして割り当てられます。 その特定の命令が実行されるたびに、JVMは対応するターゲット・メソッド・ハンドルを呼び出します。 興味深いことに、どのinvokedynamic命令のターゲットも、動的言語プログラムでの変化に応じて、時間とともに変えることが可能です。

この新しいリンケージ・メカニズムの他の要素に、ブートストラップ・メソッドがあります。 ブートストラップ・メソッドは、invokedynamic命令がリンクされるときに、命令1つにつき1回呼び出されるメソッド・ハンドルです。 ブートストラップ・メソッドは、命令に対してどのターゲット・メソッドを最初に割り当てるかを決定します。 少なくとも1つのinvokedynamic命令を含むそれぞれのクラスで、ブートストラップ・メソッドも指定する必要があります。

このブートストラップ・メカニズムによって、言語ランタイムが実行時にメソッドのディスパッチに関わることができます。さらに、このアプローチによって、メソッドのディスパッチの判定が変わらない場合に、JVMが言語ランタイムを経由せずに処理できます。 JVMは、レシーバと引数を伴うinvokedynamicバイトコードを最初に認識したときに、ブートストラップ・メソッドをコールします。 このように言語が提供するメソッドをコールすることをアップコールと呼んでいます。

次に、ブートストラップ・メソッドがコール・サイト・オブジェクトを作成し、適切なターゲット・メソッド・ハンドルを選択します。 さらに、JVMがそのメソッド・ハンドルに参照されるメソッドと、このinvokedynamicバイトコードを関連付けます。 JVMが次回invokedynamicバイトコードを実行するときには、以前に選択されたメソッドを即座に呼び出します。 ここでの考え方は、JVMが動的型付け言語のランタイム内部でアップコールを行うのは、リンクされていない動的コール・サイト、つまりはターゲット・メソッドにまだリンクされていないinvokedynamic命令に対してだけであるということです。 動的コール・サイトがいったんリンクされれば、JVMはアップコールを行わずに、ターゲット・メソッドをコールできます。

ブートストラップ・メソッドには、リンクされたinvokedynamic命令と永続的に関連付けられているjava.dyn.CallSite型のコール・サイト・オブジェクトを作成する責務もあります。 コール・サイト・オブジェクトは、ターゲット・メソッドの取得と設定のためのAPIを提供するものです。 コール・サイト・オブジェクトのサブクラスを作成して、命令をリンクするための言語固有のロジックを含めることも可能です。 特に、時間がたつと命令のターゲット・メソッドが変わる言語ランタイムでこの機能を使用できます。

メソッド・ハンドルは非常にシンプルです。 このハンドルに含まれるのは、特定の型を示すjava.dyn.MethodTypeクラスの型トークンだけです。 また、メソッド・ハンドルには、invokeメソッドが暗黙的に関連付けられています。 メソッド・ハンドルをコールするには、オブジェクト・メソッドをコールするのとほぼ同じ方法でinvokeメソッドをコールします(つまりMethodHandle.invoke(...))。 それぞれのメソッド・ハンドルには独自の型があるので、その型のinvokeコールのみが受け入れられます。 コールの型がメソッド・ハンドルの型に一致しない場合は、メソッド・ハンドルによって例外がスローされます。

メソッド・ハンドルをコールするバイトコードの例を次に示します。

   getfield myMH
   ldc #999
   invokevirtual #44 //Method java/dyn/MethodHandle:invoke(I)I
   istore 5

この例では、1つのint型引数を持つmyMHというメソッド・ハンドルに対してコールが行われます。 また、このコールは、int型の戻り型を期待しています。 メソッド・ハンドルではそのようなシグネチャを受け入れる必要があります。 JVMはコールを許可する前に、メソッド・ハンドルにコールの記述子と一致する型トークンが含まれていることを(そのたびに)検証します。この例では、(I)Iです。

応答として、メソッド・ハンドルはJVMに適切なターゲット関数の場所を指示します。 図1に示すように、メソッド・ハンドルはFooクラスのaddOneという名前の関数をポイントできます。

メソッド・ハンドル
図1. メソッド・ハンドル

前述の例のメソッド・ハンドルは、Javaメソッドを直接参照しています。これは、もっともシンプルなメソッド・ハンドルの一種です。 また、コールの過程で引数リストを調整し、引数または戻り値の挿入、変換、削除を行うことができるメソッド・ハンドルもあります。 これらのメソッド・ハンドルの作成などのために、java.dyn.MethodHandles型のAPIを使用できます。

リンクされていない動的コール・サイトに対してブートストラップ・メソッドがコールされると、次の情報が渡されます。

  • java.lang.Classオブジェクト(命令が含まれたクラス用)
  • メソッド名(Stringで表される)
  • 命令の解決済み記述子(java.dyn.MethodTypeトークンで表される)

ブートストラップ・メソッドは、コール・サイトを具現化するjava.dyn.CallSiteオブジェクトを返す必要があります。 具現化という語は、抽象概念を現実のものに変換することを表します。 コール・サイトの具現化において、コール・サイトをJavaプラットフォームにとって現実のものにするのはCallSiteオブジェクトです。 CallSiteオブジェクトには、コール・サイトのターゲット・メソッドの取得と設定を行うためのgetTargetsetTargetmethodsが含まれています。このターゲット・メソッドは、invokedynamic命令のリンケージ状態を表すメソッド・ハンドルのことです。

ブートストラップ・メソッドでは、既知のコール・サイトのテーブルなど、あらゆる言語固有のランタイム構造にコール・サイト・オブジェクトを保存することもできます。

ここで重要なのは、言語ランタイムがCallSiteオブジェクトを使用して、具現化されたコール・サイトのターゲット・メソッドを変更するということです。 要するに、ブートストラップ・メソッドは、invokedynamic命令から言語ランタイムに助けを求めるコールを受け渡すメッセンジャとして動作します。 助けを求めるコールに対する応答は、CallSiteとそのsetTarget()メソッドによって得られます。 リンケージが完了すると、メッセンジャは消失し、コール・サイトが直接ターゲット・メソッドをコールできるようになります。

メソッド・ハンドルとブートストラップ・メソッドを含むソース・コード例を次に示します。

   import java.dyn.*;

   public class Hello {
       public static void main(String... av) {
           if (av.length == 0)  av = new String[] { "world" };
           greeter(av[0] + " (from a statically linked call site)");
           for (String whom : av) {
               greeter.<void>invoke(whom);  // strongly typed direct call
               // previous line generates invokevirtual MethodHandle.invoke(String)void
               Object x = whom;
               InvokeDynamic.hail(x);               // weakly typed invokedynamic
               // previous line generates invokedynamic MethodHandle.invoke(Object)Object
           }
       }

       static void greeter(String x) { System.out.println("Hello, "+x); }
       // intentionally pun between the method and its reified handle:
       static MethodHandle greeter
               = MethodHandles.lookup().findStatic(Hello.class, "greeter",
                              MethodType.make(void.class, String.class));

       // Set up a class-local bootstrap method.
       static { Linkage.registerBootstrapMethod("bootstrapDynamic"); }
       private static CallSite bootstrapDynamic(Class caller, String name, MethodType type) {
           assert(type.parameterCount() == 1 && (Object)name == "hail");  // in lieu of MOP
           System.out.println("set target to adapt "+greeter);
           MethodHandle target = MethodHandles.convertArguments(greeter, type);
           CallSite site = new CallSite(caller, name, type);
           site.setTarget(target);
           return site;
       }
   }

この例では、HelloというJavaクラスがブートストラップ・メソッドであるbootstrapDynamicを次のように登録しています。

   // Set up a class-local bootstrap method.
   static { Linkage.registerBootstrapMethod("bootstrapDynamic"); }
  

ブートストラップ・メソッドはCallSiteを作成し、そのsetTarget()メソッドを使用して、コール・サイトのターゲットの値をメソッド・ハンドルであるgreeterに設定しています。 convertArgumentsのコールでは、コール・サイトのターゲットの型がinvokedynamic命令の記述子と一致するように、元のgreeterをラップするアダプタ・メソッド・ハンドルが作成されます。

    static MethodHandle greeter
	            = MethodHandles.lookup().findStatic(Hello.class, "greeter",
                           MethodType.make(void.class, String.class));


    private static CallSite bootstrapDynamic(Class caller, String name, MethodType type) {
       assert(type.parameterCount() == 1 && (Object)name == "hail");  // in lieu of MOP
       System.out.println("set target to adapt "+greeter);
       MethodHandle target = MethodHandles.convertArguments(greeter, type);
       CallSite site = new CallSite(caller, name, type);
       site.setTarget(target);
       return site;
    }
  

要約すると、メソッド・ハンドルにより、JVMがinvokedynamicバイトコード命令に対する応答として正しいメソッドを呼び出すことができるリンケージ・メカニズムが実現されます。 JVMは、invokedynamicバイトコードを確認すると、メソッド・ハンドルを使用して必要なメソッドに到達します。 メソッド呼出しのバイトコード要件を満たす方法としては、リフレクション呼出しアプローチよりもメソッド・ハンドルの方が優れています。 繰り返しになりますが、リフレクション呼出しアプローチでは、java.lang.reflect.Methodオブジェクトを使用してメソッドを呼び出します。 このアプローチにはシミュレーション・レイヤーが必要であり、これによって複雑さが高まり、また実行のオーバーヘッドが増加します。 これと比較してメソッド・ハンドルでは、メソッドの型や配置にかかわらず、完全に型保証された状況においてネイティブな実行速度で、メソッドに名前を付け相互に関連付けることができます。

インタフェース・インジェクションによる実行時のクラスの変更

インタフェース・インジェクションとは、クラスが新しいインタフェースを実装できるように、実行時にクラスを変更できる機能を指します。 この機能を使用すると、JVMでの動的型付け言語を他の言語と容易に統合できます。

インタフェース・インジェクションとは、クラスが新しいインタフェースを実装できるように、実行時にクラスを変更できる機能を指します。 新しいメソッドの追加は、動的型付け言語、特にスクリプト言語では一般的な機能です。 インタフェース・インジェクションにより、JVMで新しいメソッドの追加を実現する構造化された手法を得ることができます。 ただし、この機能は現時点ではJVM標準に含まれていません。 JSR 292に含めるよう検討中の段階です。

JVMでのインタフェース・インジェクションがサポートされれば、言語ランタイムは独自の目的で新しい機能をモジュール式に追加できるようになります。 たとえば、JVMで実行する言語における単独のクラスや一連の複数のクラスで、現在はその言語で実装されていないシリアライズ処理の型が必要になるとします。 このような場合、言語ランタイムでは、そのシリアライズ処理をインジェクション可能インタフェースとして定義できます。 さらに、インジェクタ・メソッドを定義できます。 そのメソッドは、新しいシリアライズ処理機能を割り当てるクラスについて知っています。 インジェクションは、初期の設定段階でも実行でき、また、処理の加えられていないクラスのインジェクション可能メソッドをJVMが最初に呼び出す段階で遅延して実行することもできます。

インタフェース・インジェクションを適用できる領域は多岐にわたります。 その1つは、JVMでの動的型付け言語を、JVMでの他の言語と容易に統合できる点です。 ここでのアプローチとしては、言語のベース・インタフェースまたはマスター・インタフェースにインジェクションを行います。 たとえば、Jythonにはorg.python.cor.PyObjectというベース・インタフェースがあります。 ベース・インタフェースにインジェクションを行うと、その後は、インジェクションする言語と他の言語のライブラリとの通信の導管としてベース・インタフェースを使用できます。

まとめ

長年にわたって、JVMは多くの言語をホストしてきており、その数は増加しています。これには、RubyやPythonなどの動的型付け言語の実装が含まれます。 JVMでの動的型付け言語のサポートは、それらの言語でアプリケーションを作成するアプリケーション開発者にとって非常に魅力的です。 その理由は、動的型付けによって開発者は高い柔軟性を得ることができ、JVMでは高い実行効率が実現されるからです。

しかし、動的型付け言語のコンパイラの実装者は、メソッド呼出しのJVMバイトコード要件を満たすことの難しさを経験してきました。 JSR 292では、invokedynamicという新しいバイトコードと、メソッド・ハンドルを基本とする新しいリンケージ・メカニズムによってこの問題に対処しています。 また、JSR 292に含めるよう検討中の機能にインタフェース・インジェクションがあります。これは、クラスが新しいインタフェースを実装できるように、実行時にクラスを変更できる機能です。

追加情報

▲ ページTOPに戻る