7 非Java言語のサポート
この章では、Java仮想マシンの非Java言語機能について説明します。
非Java言語機能の概要
Java Platform, Standard Edition (Java SE)は、次の特徴を持つアプリケーションの開発を可能にします。
- 一度書けば、どこでも実行できる
- Javaサンドボックス・セキュリティ・モデルによって安全に実行できる
- パッケージ化と配信が容易
Java SEプラットフォームは次の領域に対して堅牢なサポートを提供します。
- 同時実行性
- ガベージ・コレクション
- クラスおよびオブジェクトへのリフレクション・ベースのアクセス
- JVM Tool Interface (JVM TI): ツールで使用するためのネイティブ・プログラミング・インタフェース。JVMで実行されるアプリケーションの状態を検査する方法と、その実行を制御する方法の両方を提供します。
OracleのHotSpot JVMは次のツールおよび機能を提供します。
- DTrace: アプリケーションおよびオペレーティング・システムの動作をモニターする動的トレース・ユーティリティ。
- パフォーマンスの最適化
- PrintAssembly: バイト・コード化されたメソッドおよびネイティブ・メソッド用のアセンブリ・コードを出力するJava HotSpotオプション。
Java SE 7プラットフォームは、Java以外の言語でJVMのインフラストラクチャを利用し、パフォーマンスを潜在的に最適化することを可能にします。主要なメカニズムはinvokedynamic
命令で、動的型付け言語のコンパイラおよびランタイム・システムのJVMへの実装を簡略化します。
静的型付けと動的型付け
プログラミング言語は、コンパイル時に型チェックを実行する場合、静的に型付けされます。型チェックとは、プログラムが型安全であることを確認する処理のことです。プログラムのすべてのオペレーションの引数が正しい型であれば、そのプログラムは型安全です。
Javaは静的型付け言語です。プログラムがコンパイルされるときに、クラス変数とインスタンス変数、メソッド・パラメータ、戻り値、およびその他の変数の型情報を利用できます。Javaプログラミング言語のコンパイラはこの型情報を使用して強く型付けされたバイト・コードを生成することで、JVMでの実行時に効率的な実行が可能になります。
次のHello Worldプログラムの例は、静的型付けを示しています。型はboldで示しています。
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プログラミング言語はFixnum
型を持つ数字2を文字列に暗黙的にキャストしません。
a = "40"
b = a + 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
メソッドにリンクできます。
IntegerOps.java
class IntegerOps {
public static Integer adder(Integer x, Integer y) {
return x + y;
}
}
Example.java
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
オブジェクトはlookup
オブジェクト(メソッド・ハンドルを作成するためのファクトリ)です。
MethodHandles.Lookup.findStatic
メソッド(callerClass
lookup
オブジェクトから呼び出される)は、メソッドadder
の静的メソッド・ハンドルを作成します。
ノート: このブートストラップ・メソッドは、adder
メソッドで定義されたコードのみにinvokedynamic
コール・サイトをリンクします。このメソッドは、invokedynamic
コール・サイトに指定された引数がInteger
オブジェクトであることを前提としています。ブートストラップ・メソッドは、ブートストラップ・メソッドのパラメータ(この例ではcallerClass
、dynMethodName
、およびdynMethodType
)が変化すると実行されるコードにinvokedynamic
コール・サイトを正しくリンクするために、追加コードを必要とします。
java.lang.invoke.MethodHandles
クラスおよびjava.lang.invoke.MethodHandle
クラスには、既存のメソッド・ハンドルに基づいてメソッド・ハンドルを作成する様々なメソッドが含まれています。この例では、mh
メソッド・ハンドルのメソッド・タイプがdynMethodType
パラメータで指定されたメソッド・タイプと一致しない場合に、asType
メソッドを呼び出します。これにより、ブートストラップ・メソッドはinvokedynamic
コール・サイトをメソッド・タイプが厳密に一致しないJavaメソッドにリンクできます。
ブートストラップ・メソッドによって返されるConstantCallSite
インスタンスは、個々のinvokedynamic
命令に関連付けられているコール・サイトを表します。ConstantCallSite
インスタンスのターゲットは永続的で変更できません。この場合、1つのJavaメソッドadder
がコール・サイト実行の候補です。このメソッドはJavaメソッドでなくてもかまいません。かわりに、ランタイム・システムで使用できるこのようなメソッドがいくつかあり、それぞれが異なる引数型を処理する場合、mybsm
ブートストラップ・メソッドは、dynMethodType
引数に基づいて正しいメソッドを動的に選択できます。
invokedynamic命令
JVMでの動的型付け言語のために、invokedynamic
命令をコンパイラおよびランタイム・システムの実装で使用できます。invokedynamic
命令を使用すると、言語実装者はカスタム・リンケージを定義できます。この点は、invokevirtual
などその他のJVM命令とは異なります(Javaクラスおよびインタフェースに固有のリンケージ動作がJVMによって固定されている)。
invokedynamic
命令の各インスタンスは動的コール・サイトと呼ばれます。動的コール・サイトのインスタンスが作成されると、そのサイトはリンクされていない状態になり、呼び出すコール・サイトにはメソッドが指定されていません。動的コール・サイトは、ブートストラップ・メソッドによってメソッドにリンクされます。動的コール・サイトのブートストラップ・メソッドは、動的型付け言語のコンパイラによって指定されるメソッドです。メソッドはJVMによって1回呼び出され、サイトにリンクされます。ブートストラップ・メソッドから返されるオブジェクトは、コール・サイトのアクティビティを永続的に決定します。
invokedynamic
命令には、定数プール・インデックス(その他のinvoke
命令と同じ形式)が含まれます。定数プール・インデックスはCONSTANT_InvokeDynamic
エントリを参照します。このエントリは、ブートストラップ・メソッド(CONSTANT_MethodHandle
エントリ)、動的にリンクされるメソッドの名前、および動的にリンクされるメソッドへの呼出しの引数の型と戻り型を指定します。
次の例では、ランタイム・システムが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
タグを持つ定数プール内のエントリへの参照が含まれます。このエントリには、定数プール内のその他のエントリへの参照および属性への参照が含まれます。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
命令の定数プール・エントリには、次の値が含まれています。
CONSTANT_InvokeDynamic
タグ- 符号なしshort値
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
のメソッド・ハンドルの定数プール・エントリには次の値が含まれています。
CONSTANT_MethodHandle
タグ- 符号なしbyte値
6
- 定数プール・インデックス
#232
。
値6
,はREF_invokeStatic
サブタグです。このサブタグの詳細は、invokedynamic命令を使用するを参照してください。
invokedynamic命令を使用する
次の例は、バイト・コードがinvokedynamic
命令を使用して、動的コール・サイト(+
、加算演算子)をadder
メソッドにリンクするmybsm
ブートストラップ・メソッドを呼び出す方法を示しています。この例では、+
メソッドを使用して数字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 Javaadder
メソッドにリンクしていることに注意してください。 -
次の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
を呼び出すことで簡単に実行されます。