次の項目について説明します。
Java SE プラットフォームは、次の特徴を持つアプリケーションの開発を可能にします。
Java SE プラットフォームは次の領域およびその他の領域に対する堅牢なサポートも提供します。
Oracle の HotSpot JVM は次のツールおよび機能も提供します。
Java 以外の言語は、Java SE 7 プラットフォームを介して JVM のインフラストラクチャーを利用することで、パフォーマンスを潜在的に最適化できます。主要なメカニズムは invokedynamic
命令で、動的型付け言語のコンパイラおよびランタイムシステムの JVM への実装を簡略化します。
プログラミング言語は、コンパイル時に型チェックを実行する場合、静的に型付けされます。型チェックとは、プログラムが型安全であることを確認する処理のことです。プログラムのすべてのオペレーションの引数が正しい型であれば、そのプログラムは型安全です。
Java は静的型付け言語です。クラス変数とインスタンス変数、メソッドパラメータ、戻り値、およびその他の変数のすべての型付け情報は、プログラムがコンパイルされるときに利用されます。Java プログラミング言語のコンパイラはこの型情報を使用して強く型付けされたバイトコードを生成することで、JVM での実行時に効率的な実行が可能になります。
次の Hello World プログラムの例は、静的型付けを示しています。型は太字で示しています。
import java.util.Date; public class HelloWorld { public static void main(String[] argv) { String hello = "Hello "; Date currDate = new Date(); for (String a : argv) { System.out.println(hello + a); System.out.println("Today's date is: " + currDate); } } }
プログラミング言語は、実行時に型チェックを実行する場合、動的に型付けされます。JavaScript と Ruby は動的型付け言語の例です。これらの言語は、アプリケーション内の値が予期する型に一致することを、コンパイル時ではなく実行時に確認します。これらの言語は通常、コンパイル時に使用できる型情報を持ちません。オブジェクトの型は実行時にのみ判別できます。そのため以前は、JVM 上でこれらを効率的に実装するのは困難でした。
次は、Ruby プログラミング言語で記述された Hello World プログラムの例です。
#!/usr/bin/env ruby require 'date' hello = "Hello " currDate = DateTime.now ARGV.each do|a| puts hello + a puts "Date and time: " + currDate.to_s end
すべての名前が型宣言なしで導入されています。また、メインプログラムはホルダー型 (Java クラス HelloWorld
) の中にはありません。Ruby で Java for
ループに相当するものは、変数 ARGV
の動的な型の中にあります。ループの本体は、動的な言語に共通する特徴である、クロージャーと呼ばれるブロックに含まれます。
強い型付けを特徴とするプログラミング言語は、そのオペレーションに提供される値の型に制限を指定します。強い型付けを実装するコンピュータ言語は、引数が間違った型を持っている場合、オペレーションの実行を妨げます。逆に、弱い型付けを特徴とする言語は、オペレーションの引数が間違った型または互換性のない型を持つ場合、これらの引数を暗黙的に変換 (キャスト) します。
静的型付けプログラミング言語は、強い型付けまたは弱い型付けを使用できます。同様に、動的型付け言語も、強い型付けまたは弱い型付けを適用できます。たとえば、プログラミング言語 Ruby は動的に型付けされ、かつ強く型付けされます。変数がある型の値で初期化されると、プログラミング言語 Ruby は変数を別のデータ型に暗黙的に変換しません。プログラミング言語 Ruby は次を許可しません。
a = "40" b = a + 2
この例では、プログラミング言語 Ruby は Fixnum
型を持つ数字 2 を文字列に暗黙的にキャストしません。
2 つの数字 (どの数値型でも構いません) を追加してその合計を返す、次の動的型付けメソッド addtwo
について考えます。
def addtwo(a, b) a + b; end
あなたの組織が、メソッド addtwo
が記述されたプログラミング言語用のコンパイラとランタイムシステムを実装しているとします。静的型付けか動的型付けかにかかわらず、強く型付けされた言語では、+
(加算演算子) の動作はオペランドの型によって決まります。静的型付け言語のコンパイラは、a
および b
の静的型に基づいて、+
のどの実装が適切かを選択します。たとえば、a
および b
の型が int
の場合、Java コンパイラは JVM の iadd
命令で +
を実装します。JVM の iadd
命令は静的に認識されるオペランド型を必要とするため、この加算演算子はメソッド呼び出しにコンパイルされます。
これに対し、動的型付け言語のコンパイラは実行時まで選択を保留する必要があります。文 a + b
はメソッド呼び出し +(a, b)
としてコンパイルされます (+
はメソッド名)。(JVM では +
という名前のメソッドが許可されますが、Java プログラミング言語では許可されません。) この動的型付け言語用のランタイムシステムが、a
および b
が整数型の変数であることを識別できるとします。ランタイムシステムは、任意のオブジェクト型ではなく整数型に専用化された +
の実装を呼び出すことを選択します。
動的型付け言語のコンパイルの課題は、プログラムがコンパイルされたあとに、メソッドまたは関数の最適な実装を選択できるランタイムシステムをどのように実装するかです。すべての変数を Object
型のオブジェクトして扱うと効率的に機能しません。Object
クラスに +
という名前のメソッドが含まれていないためです。
Java SE 7 は、ランタイムシステムがコールサイトとメソッド実装との間のリンケージをカスタマイズできる、invokedynamic
命令を導入しています。この例では、invokedynamic
コールサイトは +
です。invokedynamic
コールサイトは、ブートストラップメソッド (サイトをリンクするために JVM によって 1 回呼び出される、動的型付け言語のコンパイラによって指定されるメソッド) によって、メソッドにリンクされます。+
を呼び出す invokedynamic
命令をコンパイラが発行したと想定し、かつランタイムシステムがメソッド adder(Integer,Integer)
を認識すると想定したうえで、ランタイムは次のように invokedynamic
コールサイトを adder
メソッドにリンクできます。
class IntegerOps { public static Integer adder(Integer x, Integer y) { return x + y; } }
import java.util.*; import java.lang.invoke.*; import static java.lang.invoke.MethodType.*; import static java.lang.invoke.MethodHandles.*; class Example { public static CallSite mybsm( MethodHandles.Lookup callerClass, String dynMethodName, MethodType dynMethodType) throws Throwable { MethodHandle mh = callerClass.findStatic( Example.class, "IntegerOps.adder", MethodType.methodType(Integer.class, Integer.class, Integer.class)); if (!dynMethodType.equals(mh.type())) { mh = mh.asType(dynMethodType); } return new ConstantCallSite(mh); } }
この例では、IntegerOps
クラスは動的言語のランタイムシステムに付属のライブラリに属します。
メソッド Example.mybsm
は、invokedynamic
コールサイトを adder
メソッドにリンクするブートストラップメソッドです。
オブジェクト callerClass
はルックアップオブジェクト (メソッドハンドルを作成するためのファクトリ) です。
メソッド MethodHandles.Lookup.findStatic
(callerClass
ルックアップオブジェクトから呼び出される) は、メソッド adder
の static メソッドハンドルを作成します。
注:このブートストラップメソッドは adder
メソッドで定義されたコードにのみ invokedynamic
コールサイトをリンクし、invokedynamic
コールサイトに指定される引数は Integer
オブジェクトであると想定します。ブートストラップメソッドは、ブートストラップメソッドのパラメータ (この例では callerClass
、dynMethodName
、および dynMethodType
) が変化すると実行されるコードに invokedynamic
コールサイトを正しくリンクするために、追加コードを必要とします。
クラス java.lang.invoke.MethodHandles
および java.lang.invoke.MethodHandle
には、既存のメソッドハンドルに基づいてメソッドハンドルを作成するさまざまなメソッドが含まれています。この例では、メソッドハンドル mh
のメソッドタイプがパラメーター dynMethodType
で指定されたメソッドタイプと一致しない場合に、メソッド asType
を呼び出します。これにより、ブートストラップメソッドは invokedynamic
コールサイトをメソッドタイプが厳密に一致しない Java メソッドにリンクできます。
ブートストラップメソッドによって返される ConstantCallSite
インスタンスは、個々の invokedynamic
命令に関連付けられているコールサイトを表します。ConstantCallSite
インスタンスのターゲットは永続的で変更できません。この場合、Java メソッドは adder
1 つのみ存在します (コールサイト実行の候補)。このメソッドは Java メソッドでなくてもかまいません。代わりに、ランタイムシステムで使用できるこのようなメソッドがいくつかあり、それぞれが異なる引数型を処理する場合、ブートストラップメソッド mybsm
は、dynMethodType
引数に基づいて正しいメソッドを動的に選択できます。
invokedynamic
命令は、動的言語のコンパイラおよびランタイムシステムの JVM 上への実装を簡略化し、潜在的に改善します。invokedynamic
命令は、言語実装者がカスタムリンケージ動作を定義することを許可することで、これを行います。この点は、invokevirtual
などその他の JVM 命令とは異なります (Java クラスおよびインタフェースに固有のリンケージ動作が JVM によって固定されている)。
invokedynamic
命令の各インスタンスは動的コールサイトと呼ばれます。動的コールサイトは、最初はリンクされていない状態 (呼び出すコールサイトにメソッドが指定されていない) です。前述のように、動的コールサイトはブートストラップメソッドによってメソッドにリンクされます。動的コールサイトのブートストラップメソッドは、動的型付け言語のコンパイラによって指定されるメソッドで、サイトをリンクするために JVM によって 1 回呼び出されます。ブートストラップメソッドから返されるオブジェクトは、コールサイトの動作を永続的に決定します。
invokedynamic
命令には、定数プールインデックス (その他の invoke
命令と同じ形式) が含まれます。定数プールインデックスは CONSTANT_InvokeDynamic
エントリを参照します。このエントリは、ブートストラップメソッド (CONSTANT_MethodHandle
エントリ)、動的にリンクされるメソッドの名前、および動的にリンクされるメソッドへの呼び出しの引数の型および戻り型を指定します。
次は、invokedynamic
命令の例です。この例では、ランタイムシステムがブートストラップメソッド Example.mybsm
を使用することで、この invokedynamic
命令 (+
、加算演算子) で指定された動的コールサイトを IntegerOps.adder
メソッドにリンクします。メソッド adder
および mybsm
は、「動的型付け言語のコンパイルの課題」セクションで定義されています (わかりやすくするために改行が追加されています)。
invokedynamic InvokeDynamic REF_invokeStatic: Example.mybsm: "(Ljava/lang/invoke/MethodHandles/Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;": +: "(Ljava/lang/Integer; Ljava/lang/Integer;) Ljava/lang/Integer;";
注:これらのセクションのバイトコード例は、ASM Java バイトコード操作および分析フレームワークの構文を使用しています。
動的にリンクされるメソッドを invokedynamic
命令で呼び出すには、次の手順が必要です。
実行時に、JVM がはじめて invokedynamic
を検出すると、ブートストラップメソッドが呼び出されます。このメソッドは、invokedynamic
命令によって指定された名前を、実行されるべきコード (ターゲットメソッド、メソッドハンドルによって参照される) にリンクします。JVM が同じ invokedynamic
命令を再度実行した場合、ブートストラップメソッドを呼び出さず、リンクされたメソッドハンドルを自動的に呼び出します。
ブートストラップメソッドの戻り型は java.lang.invoke.CallSite
である必要があります。CallSite
オブジェクトは、invokedynamic
命令とそれがリンクされるメソッドハンドルの、リンク状態を表します。
ブートストラップメソッドは 3 つ以上のパラメータを取ります。
MethodHandles.Lookup
オブジェクト、invokedynamic
命令のコンテキスト内でメソッドハンドルを作成するファクトリ。String
オブジェクト、動的コールサイト内で言及されるメソッド名。MethodType
オブジェクト、動的コールサイトの解決済み型シグニチャー。invokedynamic
命令への 1 つ以上の追加静的引数。定数プールから取り出されるこれらの引数の目的は、言語実装者がブートストラップメソッドに便利な追加メタデータを安全かつコンパクトにエンコードするのを支援することです。各コールサイトには独自のブートストラップメソッドが指定される可能性があるため、原則として名前と追加引数は冗長になります。ただし、そのような運用ではおそらく、大きなクラスファイルや定数プールが生成されます。ブートストラップメソッドの例については、「動的型付け言語のコンパイルの課題」セクションを参照してください。
前述のように、invokedynamic
命令にはタグ CONSTANT_InvokeDynamic
を持つ定数プール内のエントリへの参照が含まれます。このエントリには、定数プール内のその他のエントリへの参照および属性への参照が含まれます。このセクションでは、invokedynamic
命令で使用される定数プールエントリについて簡単に説明します。詳細は、java.lang.invoke パッケージのドキュメントおよび「Java 仮想マシン仕様」を参照してください。
次は、メソッド +
を Java メソッド adder
にリンクするブートストラップメソッド Example.mybsm
を含む、クラス Example
の定数プールからの引用です。
class #159; // #47 Utf8 "adder"; // #83 Utf8 "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Integer;"; // #84 Utf8 "mybsm"; // #87 Utf8 "(Ljava/lang/invoke/MethodHandles/Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;) java/lang/invoke/CallSite;"; // #88 Utf8 "Example"; // #159 Utf8 "+"; // #166 // ... NameAndType #83 #84; // #228 Method #47 #228; // #229 MethodHandle 6b #229; // #230 NameAndType #87 #88; // #231 Method #47 #231; // #232 MethodHandle 6b #232; // #233 NameAndType #166 #84; // #234 Utf8 "BootstrapMethods"; // #235 InvokeDynamic 0s #234; // #236
この例の invokedynamic
命令の定数プールエントリには、3 つの値が含まれています。
CONSTANT_InvokeDynamic
タグ0
#234
。値 0
は、BootstrapMethods
属性に格納されている指定子配列内の最初のブートストラップメソッド指定子を参照します。ブートストラップメソッド指定子は、定数プールテーブル内にはありません。この独立した指定子配列に含まれています。各ブートストラップメソッド指定子には CONSTANT_MethodHandle
定数プールエントリ (ブートストラップメソッドそのもの) へのインデックスが含まれています。
次は、BootstrapMethods
属性を示す、同じ定数プールからの引用で、ブートストラップメソッド指定子の配列が含まれています。
[3] { // Attributes // ... Attr(#235, 6) { // BootstrapMethods at 0x0F63 [1] { // bootstrap_methods { // bootstrap_method #233; // bootstrap_method_ref [0] { // bootstrap_arguments } // bootstrap_arguments } // bootstrap_method } } // end BootstrapMethods } // Attributes
ブートストラップメソッド mybsm
のメソッドハンドルの定数プールエントリには 3 つの値が含まれています。
CONSTANT_MethodHandle
タグ6
#232
。値 6
はサブタグ REF_invokeStatic
です。このサブタグの詳細については、次のセクション「3. invokedynamic 命令を使用する」を参照してください。
次のバイトコードは invokedynamic
命令を使用して、ブートストラップメソッド mybsm
(動的コールサイト (+
、加算演算子) をメソッド adder
にリンク) を呼び出します。この例では、+
メソッドを使用して数字 40
と 2
を追加しています (わかりやすくするために改行が挿入されています)。
bipush 40; invokestatic Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;"; iconst_2; invokestatic Method java/lang/Integer.valueOf:"(I)Ljava/lang/Integer;"; invokedynamic InvokeDynamic REF_invokeStatic: Example.mybsm: "(Ljava/lang/invoke/MethodHandles/Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;": +: "(Ljava/lang/Integer; Ljava/lang/Integer;) Ljava/lang/Integer;";
最初の 4 つの命令は、整数 40
と 2
をスタック上に置き、それらを java.lang.Integer
ラッパー型に Autoboxing します。5 つ目の命令が動的メソッドを呼び出します。この命令は、CONSTANT_InvokeDynamic
タグを持つ定数プールエントリを参照します。
REF_invokeStatic: Example.mybsm: "(Ljava/lang/invoke/MethodHandles/Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;": +: "(Ljava/lang/Integer; Ljava/lang/Integer;) Ljava/lang/Integer;";
このエントリでは CONSTANT_InvokeDynamic
タグに 4 つのバイトが続きます。
最初の 2 つのバイトが、ブートストラップメソッド指定子を参照する CONSTANT_MethodHandle
エントリへの参照を構成しています。
REF_invokeStatic: Example.mybsm: "(Ljava/lang/invoke/MethodHandles/Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;"
前述のように、ブートストラップメソッド指定子へのこの参照は、定数プールテーブル内にはありません。BootstrapMethods
と呼ばれるクラスファイル属性によって定義された、独立した配列に含まれます。ブートストラップメソッド指定子には CONSTANT_MethodHandle
定数プールエントリ (ブートストラップメソッドそのもの) へのインデックスが含まれています。
この CONSTANT_MethodHandle
定数プールエントリには 3 つのバイトが続きます。
最初のバイトはサブタグ REF_invokeStatic
です。これは、このブートストラップメソッドが static メソッドのメソッドハンドルを作成することを意味します。このブートストラップメソッドが動的コールサイトを static Java メソッド adder
にリンクしています。
次の 2 つのバイトは、メソッドハンドル作成対象のメソッドを表す、CONSTANT_Methodref
エントリを構成しています。
Example.mybsm: "(Ljava/lang/invoke/MethodHandles/Lookup; Ljava/lang/String; Ljava/lang/invoke/MethodType;) Ljava/lang/invoke/CallSite;"
この例では、ブートストラップメソッドの完全指定名が Example.mybsm
、引数型が MethodHandles.Lookup
、String
、および MethodType
、戻り型が CallSite
です。
次の 2 つのバイトが CONSTANT_NameAndType
エントリへの参照を構成しています。
+: "(Ljava/lang/Integer; Ljava/lang/Integer;) Ljava/lang/Integer;"
この定数プールエントリは、メソッド名 (+
)、引数型 (2 つの Integer
インスタンス)、および動的コールサイトの戻り型 (Integer
) を指定しています。
この例では、動的コールサイトには Autoboxing された整数値 (最終ターゲットである adder
メソッドの型に厳密に一致) が渡されます。実際には、引数型と戻り型が厳密に一致する必要はありません。たとえば、invokedynamic
命令は JVM スタックにそのオペランドの一方または両方をプリミティブ型 int
値として渡すことができます。オペランドの一方または両方が型指定のない Object
値でもかまいません。invokedynamic
命令は、その結果をプリミティブ型 int
値または型指定のない Object
値として受け取ることもできます。どの場合も、mybsm
の dynMethodType
引数が invokedynamic
命令によって要求されるメソッド型を正確に記述します。
adder
メソッドに渡されるのは、プリミティブ型でも、型指定のない引数でも、戻り値でもかまいません。ブートストラップメソッドが、dynMethodType
と adder
メソッドの型の違いを解決します。コードに示すように、これはターゲットメソッドで asType
を呼び出すことで簡単に実行されます。