言語の埋込み
- ポリグロット・アプリケーションのコンパイルおよび実行
- ゲスト言語関数のJava値としての定義
- Javaからゲスト言語への直接アクセス
- ゲスト言語からJavaへのアクセス
- ゲスト言語からのJava型のルックアップ
- ポリグロット・プロキシを使用して計算された配列
- ホスト・アクセス
- ポリグロット・アプリケーションからのネイティブ実行ファイルのビルド
- 複数のコンテキストにわたるコード・キャッシング
- ゲスト言語に埋め込まれた言語
- 多数の言語用のシェルのビルド
- 実行リスナーを使用したステップ実行
- 依存性の設定
GraalVMポリグロットAPIを使用すると、JVMベースのホスト・アプリケーションにゲスト言語のコードを埋め込んで実行できます。
この項では、GraalVMで実行され、ゲスト言語を直接コールするホスト・アプリケーションをJavaで作成する方法を学習します。各コード例の下にあるタブを使用して、JavaScript、R、RubyおよびPythonのいずれかを選択できます。
始める前に、必ずGraalVMを設定してください。
ポリグロット・アプリケーションのコンパイルおよび実行
GraalVMでは、Truffle言語実装フレームワークで実装された任意の言語で記述されたポリグロット・アプリケーションを実行できます。これらの言語は、以降、ゲスト言語と呼びます。
この項のステップを実行して、GraalVMで実行され、プログラミング言語の相互運用性を示すサンプルのポリグロット・アプリケーションを作成します。
1. hello-polyglot
プロジェクト・ディレクトリを作成します。
2. プロジェクト・ディレクトリで、次のコードを含むHelloPolyglot.java
ファイルを追加します:
// COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET
public class hello_polyglot_js {
static
// BEGIN-SNIPPET
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello Java!");
try (Context context = Context.create()) {
context.eval("js", "print('Hello JavaScript!');");
}
}
}
// END-SNIPPET
public static void main(String[] args) {
HelloPolyglot.main(null);
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET
public class hello_polyglot_R {
static
// BEGIN-SNIPPET
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello Java!");
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.eval("R", "print('Hello R!');");
}
}
}
// END-SNIPPET
public static void main(String[] args) {
HelloPolyglot.main(null);
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET
public class hello_polyglot_ruby {
static
// BEGIN-SNIPPET
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello Java!");
try (Context context = Context.create()) {
context.eval("ruby", "puts 'Hello Ruby!'");
}
}
}
// END-SNIPPET
public static void main(String[] args) {
HelloPolyglot.main(null);
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java {file}
// BEGIN-SNIPPET
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
// END-SNIPPET
public class hello_polyglot_python {
static
// BEGIN-SNIPPET
public class HelloPolyglot {
public static void main(String[] args) {
System.out.println("Hello Java!");
try (Context context = Context.create()) {
context.eval("python", "print('Hello Python!')");
}
}
}
// END-SNIPPET
public static void main(String[] args) {
HelloPolyglot.main(null);
}
}
このコードでは:
import org.graalvm.polyglot.*
で、ポリグロットAPIのベースAPIをインポートします。import org.graalvm.polyglot.proxy.*
で、後の例で必要なポリグロットAPIのプロキシ・クラスをインポートします。Context
で、ゲスト言語の実行環境を提供します。Rでは、現在、この例を実行するにはallowAllAccess
フラグをtrue
に設定する必要があります。eval
で、ゲスト言語コードの指定されたスニペットを評価します。- リソース文を含む
try
で、Context
を初期化し、使用後に確実にクローズするようにします。コンテキストをクローズすると、潜在的なネイティブ・リソースを含むすべてのリソースが即時に解放されます。コンテキストをクローズすることはオプションですが、お薦めします。クローズしないまま参照されなくなったコンテキストも、ガベージ・コレクタによって自動的に解放されます。
3. javac HelloPolyglot.java
を実行してGraalVMでHelloPolyglot.java
をコンパイルします。
4. java HelloPolyglot
を実行してGraalVMでアプリケーションを実行します。
これで、GraalVMで実行されるJavaホスト・アプリケーションとゲスト言語コードで構成されたポリグロット・アプリケーションが作成されました。このアプリケーションを他のコード例で使用して、ポリグロットAPIのさらに高度な機能をデモンストレーションできます。
この項の他のコード例を使用するには、単に、次のことを実行する必要があります:
1. コード・スニペットをHelloPolyglot.java
のメイン・メソッドに追加します。
2. ポリグロット・アプリケーションをコンパイルして実行します。
ゲスト言語関数のJava値としての定義
ポリグロット・アプリケーションを使用すると、あるプログラミング言語から値を取得し、それを他の言語で使用できます。
この項のコード例をポリグロット・アプリケーションで使用して、ポリグロットAPIがJavaScript、R、RubyまたはPython関数をJava値としてどのように戻すことができるかを示します。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_js {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("js", "x => x+1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_R {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
Value function = context.eval("R", "function(x) x + 1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_ruby {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("ruby", "proc { |x| x + 1 }");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class function_python {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value function = context.eval("python", "lambda x: x + 1");
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
// END-SNIPPET
}
}
このコードでは:
Value function
は、関数を参照するJava値です。eval
コールは、スクリプトを解析してゲスト言語関数を戻します。- 最初のアサーションは、コード・スニペットによって戻された値が実行可能であることを確認します。
execute
コールは、関数を引数41
で実行します。asInt
コールは、結果をJavaint
に変換します。- 2番目のアサーションは、結果が予期したとおりに1増分されたことを検証します。
Javaからゲスト言語への直接アクセス
ポリグロット・アプリケーションは、ほとんどの言語の型へのアクセスに対応し、関数に限定されません。Javaなどのホスト言語は、ポリグロット・アプリケーションに埋め込まれたゲスト言語の値に直接アクセスできます。
この項のコード例をポリグロット・アプリケーションで使用して、ポリグロットAPIがオブジェクト、数値、文字列および配列にどのようにアクセスできるかを示します。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_js_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("js",
"({ " +
"id : 42, " +
"text : '42', " +
"arr : [1,42,3] " +
"})");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_R_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
Value result = context.eval("R",
"list(" +
"id = 42, " +
"text = '42', " +
"arr = c(1,42,3)" +
")");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_ruby_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("ruby",
"o = Struct.new(:id, :text, :arr).new(" +
"42, " +
"'42', " +
"[1,42,3] " +
")");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
public class access_python_from_java {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.create()) {
Value result = context.eval("python",
"type('obj', (object,), {" +
"'id' : 42, " +
"'text': '42', " +
"'arr' : [1,42,3]" +
"})()");
assert result.hasMembers();
int id = result.getMember("id").asInt();
assert id == 42;
String text = result.getMember("text").asString();
assert text.equals("42");
Value array = result.getMember("arr");
assert array.hasArrayElements();
assert array.getArraySize() == 3;
assert array.getArrayElement(1).asInt() == 42;
}
// END-SNIPPET
}
}
このコードでは:
Value result
は、3つのメンバー(id
という数値、text
という文字列およびarr
という配列)を含むオブジェクトです。- 最初のアサーションは、戻り値にメンバーを含めることができることを検証し、これは値がオブジェクトのような構造であることを示します。
id
変数は、結果のオブジェクトからid
という名前のメンバーを読み取ることによって初期化されます。結果は、asInt()
を使用してJavaint
に変換されます。- 次のアサーションは、結果が
42
という値であることを検証します。 text
変数は、メンバーtext
の値を使用して初期化され、これもasString()
を使用してJavaString
に変換されます。- 次のアサーションは、結果値がJava
String
"42"
と等しいことを検証します。 - 次に、配列を保持する
arr
メンバーが読み取られます。 - 配列は、
hasArrayElements
に対してtrue
を返します。Rの配列インスタンスは、メンバーと配列要素を同時に持つことができます。 - 次のアサーションは、配列のサイズが3に等しいことを検証します。ポリグロットAPIは大きい配列をサポートしているため、配列長は
long
型になります。 - 最後に、インデックス
1
の配列要素が42
と等しいことを検証します。インデックスが1から始まるRなどの言語の場合でも、ポリグロット値を使用した配列インデックス付けは常にゼロベースです。
ゲスト言語からJavaへのアクセス
ポリグロット・アプリケーションは、ゲスト言語とホスト言語の間の双方向アクセスを提供します。その結果、Javaオブジェクトをゲスト言語に渡すことができます。
ポリグロットAPIはデフォルトでセキュアであるため、デフォルトの構成ではアクセスが制限されています。Javaオブジェクトのパブリック・メソッドまたはパブリック・フィールドへのアクセスをゲスト言語に許可するには、コンテキストのビルド時にallowAllAccess(true)
を明示的に指定する必要があります。このモードでは、ゲスト言語コードは、ホストJavaコードがアクセスできるすべてのリソースにアクセスできます。
この項のコード例をポリグロット・アプリケーションで使用して、ゲスト言語がプリミティブJava値、オブジェクト、配列および関数型インタフェースにどのようにアクセスできるかを示します。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_js {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("js").putMember("javaObj", new MyClass());
boolean valid = context.eval("js",
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_R {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("R").putMember("javaObj", new MyClass());
boolean valid = context.eval("R",
" javaObj$id == 42" +
" && javaObj$text == '42'" +
" && javaObj$arr[[2]] == 42" +
" && javaObj$ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_ruby {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getPolyglotBindings().putMember("javaObj", new MyClass());
boolean valid = context.eval("ruby",
"javaObj = Polyglot.import('javaObj')\n" +
" javaObj[:id] == 42" +
" && javaObj[:text] == '42'" +
" && javaObj[:arr][1] == 42" +
" && javaObj[:ret42].call == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import java.util.concurrent.Callable;
import org.graalvm.polyglot.*;
public class access_java_from_python {
// BEGIN-SNIPPET
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getPolyglotBindings().putMember("javaObj", new MyClass());
boolean valid = context.eval("python",
"import polyglot \n" +
"javaObj = polyglot.import_value('javaObj')\n" +
"javaObj.id == 42" +
" and javaObj.text == '42'" +
" and javaObj.arr[1] == 42" +
" and javaObj.ret42() == 42")
.asBoolean();
assert valid == true;
}
}
// END-SNIPPET
}
このコードでは:
- Javaクラス
MyClass
には、4つのpublicフィールドid
、text
、arr
およびret42
があります。フィールドは、常に42
のint
値を戻す42
、"42"
、new int[]{1, 42, 3}
およびラムダ() -> 42
で初期化されます。 - Javaクラス
MyClass
はインスタンス化され、名前javaObj
でポリグロット・スコープにエクスポートされ、これによりホスト言語およびゲスト言語はシンボルを交換できます。 javaObj
シンボルをインポートして同じくjavaObj
という名前のローカル変数に割り当てるゲスト言語スクリプトが評価されます。変数との競合を回避するには、ポリグロット・スコープのすべての値を言語の最上位スコープで明示的にインポートおよびエクスポートする必要があります。- 次の2行では、Javaオブジェクトの内容を数値
42
および文字列'42'
と比較して検証します。 - 3番目の検証では、2番目の配列位置から読み取って数値
42
と比較します。配列が0ベースまたは1ベースのどちらのインデックス付けを使用してアクセスされるかは、ゲスト言語によって異なります。言語に関係なく、arr
フィールドに格納されたJava配列は常に変換された0ベースのインデックスを使用してアクセスされます。たとえば、R言語では、配列は1ベースであるため、2番目の配列要素にはインデックス2
を使用してアクセスできます。JavaScriptおよびRuby言語では、2番目の配列要素はインデックス1
にあります。すべての言語の例で、Java配列は同じインデックス1
を使用して読み取られます。 - 最後の行で、フィールド
ret42
に含まれるJavaラムダが起動され、結果が数値42
と比較されます。 - ゲスト言語スクリプトの実行後、検証が実行され、スクリプトが結果として
true
のboolean
値を返すことが確認されます。
ゲスト言語からのJava型のルックアップ
ゲスト言語にJavaオブジェクトを渡す以外に、ゲスト言語でのJava型のルックアップを許可できます。
この項のコード例をポリグロット・アプリケーションで使用して、ゲスト言語でどのようにJava型をルックアップしてインスタンス化するかを示します。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_js {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("js",
"var BigDecimal = Java.type('java.math.BigDecimal');" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_R {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("R",
"BigDecimal = java.type('java.math.BigDecimal');\n" +
"BigDecimal$valueOf(10)$pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_ruby {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("ruby",
"BigDecimal = Java.type('java.math.BigDecimal')\n" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
public class lookup_java_from_python {
public static void main(String[] args) {
// BEGIN-SNIPPET
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
java.math.BigDecimal v = context.eval("python",
"import java\n" +
"BigDecimal = java.type('java.math.BigDecimal')\n" +
"BigDecimal.valueOf(10).pow(20)")
.asHostObject();
assert v.toString().equals("100000000000000000000");
}
// END-SNIPPET
}
}
このコードでは:
- すべてのアクセスを有効にして(
allowAllAccess(true)
)新しいコンテキストが作成されます。 - ゲスト言語スクリプトが評価されます。
- スクリプトは、Java型
java.math.BigDecimal
をルックアップし、BigDecimal
という名前の変数に格納します。 - staticメソッド
BigDecimal.valueOf(long)
が起動され、値10
を使用して新しいBigDecimal
が作成されます。static Javaメソッドをルックアップする以外に、戻されたJava型を直接インスタンス化する(たとえば、JavaScriptではnew
キーワードを使用して)こともできます。 20
を指定したpow
インスタンス・メソッドを起動して10^20
を計算するために、新しい小数が使用されます。- スクリプトの結果は、
asHostObject()
をコールしてホスト・オブジェクトに変換されます。戻り値は、自動的にBigDecimal
型にキャストされます。 - 結果の10進文字列は、
"100000000000000000000"
と等しいとアサートされます。
ポリグロット・プロキシを使用して計算された配列
ポリグロットAPIには、オブジェクト、配列、ネイティブ・オブジェクト、プリミティブなどのゲスト言語の型を模倣することでJavaの相互運用性をカスタマイズできるポリグロット・プロキシ・インタフェースが含まれています。
この項のコード例をポリグロット・アプリケーションで使用して、値を遅延計算する配列の実装方法を確認します。
ノート: ポリグロットAPIは、JVM上またはネイティブ・イメージのいずれかでポリグロット・プロキシをサポートしています。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
public class proxy_js {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.create()) {
ComputedArray arr = new ComputedArray();
context.getBindings("js").putMember("arr", arr);
long result = context.eval("js",
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_R {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("R",
"arr <- import('arr');" +
"arr[2] + arr[1000000001]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_ruby {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("ruby",
"arr = Polyglot.import('arr') \n" +
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.proxy.ProxyArray;
public class proxy_python {
// BEGIN-SNIPPET
static class ComputedArray implements ProxyArray {
public Object get(long index) {
return index * 2;
}
public void set(long index, Value value) {
throw new UnsupportedOperationException();
}
public long getSize() {
return Long.MAX_VALUE;
}
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
ComputedArray arr = new ComputedArray();
context.getPolyglotBindings().putMember("arr", arr);
long result = context.eval("python",
"import polyglot\n" +
"arr = polyglot.import_value('arr') \n" +
"arr[1] + arr[1000000000]")
.asLong();
assert result == 2000000002L;
}
}
// END-SNIPPET
}
このコードでは:
- Javaクラス
ComputedArray
で実装されるプロキシ・インタフェースProxyArray
によって、ゲスト言語がJavaクラスのインスタンスを配列のように扱われるようにします。 ComputedArray
配列では、メソッドget
をオーバーライドし、算術式を使用して値を計算します。- 配列プロキシは書込みアクセスをサポートしません。このため、
set
の実装でUnsupportedOperationException
がスローされます。 getSize
の実装によって、その長さとしてLong.MAX_VALUE
が戻されます。- メイン・メソッドで、新しいポリグロット実行コンテキストを作成します。
ComputedArray
クラスの新しいインスタンスがarr
という名前を使用してエクスポートされます。- ゲスト言語スクリプトによって
arr
シンボルがインポートされ、これにより、エクスポートされたプロキシが戻されます。 - 2番目の要素と
1000000000
番目の要素がアクセスされ、合計されてから戻されます。Rなどの1ベースの言語の配列インデックスは、プロキシ配列の0ベースのインデックスに変換されることに注意してください。 - 言語スクリプトの結果は、long値として戻されて検証されます。
ポリグロット・プロキシ・インタフェースの詳細は、ポリグロットAPIのJavaDocを参照してください。
ホスト・アクセス
ポリグロットAPIでは、デフォルトで、ファイルI/Oなどの特定の重要な機能へのアクセスが制限されます。これらの制限は、allowAllAccess
をtrue
に設定することで完全に解除できます。
ノート: 現在、アクセス制限はJavaScriptでのみサポートされています。
ホスト機能へのアクセスの制御
ホストへのゲスト・アプリケーションのアクセスを制限することが望ましい場合があります。たとえば、System.exit
をコールするJavaメソッドが公開されている場合、ゲスト・アプリケーションはホスト・プロセスを終了できます。メソッドが誤って公開されることを回避するために、ホスト・アクセスはデフォルトでは許可されておらず、すべてのpublicメソッドまたはフィールドには明示的に@HostAccess.Export
注釈を付ける必要があります。
// COMPILE-CMD: javac {file}
// RUN-CMD: java -ea {file}
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.HostAccess;
import org.graalvm.polyglot.PolyglotException;
public class explicit_access_java_from_js {
static
// BEGIN-SNIPPET
public class Employee {
private final String name;
Employee(String name) {this.name = name;}
@HostAccess.Export
public String getName() {
return name;
}
}
//END-SNIPPET
static
//BEGIN-SNIPPET
public class Services {
@HostAccess.Export
public Employee createEmployee(String name) {
return new Employee(name);
}
public void exitVM() {
System.exit(1);
}
}
public static void main(String[] args) {
try (Context context = Context.create()) {
Services services = new Services();
context.getBindings("js").putMember("services", services);
String name = context.eval("js",
"let emp = services.createEmployee('John Doe');" +
"emp.getName()").asString();
assert name.equals("John Doe");
try {
context.eval("js", "services.exitVM()");
assert false;
} catch (PolyglotException e) {
assert e.getMessage().endsWith(
"Unknown identifier: exitVM");
}
}
}
// END-SNIPPET
}
このコードでは:
- クラス
Employee
が、String
型のフィールドname
を使用して宣言されます。getName
メソッドへのアクセスは、メソッドに@HostAccess.Export
注釈を付けることで明示的に許可されます。 Services
クラスは、createEmployee
およびexitVM
という2つのメソッドを公開します。createEmployee
メソッドは、引数として従業員の名前を取得し、新しいEmployee
インスタンスを作成します。createEmployee
メソッドには@HostAccess.Export
注釈が付けられているため、ゲスト・アプリケーションからアクセスできます。exitVM
メソッドは明示的にエクスポートされていないため、アクセスできません。main
メソッドは、最初にデフォルト構成で新しいポリグロット・コンテキストを作成し、@HostAccess.Export
注釈が付けられたメソッドを除いてホスト・アクセスを禁止します。- 新しい
Services
インスタンスが作成され、グローバル変数services
としてコンテキストに配置されます。 - 最初に評価されるスクリプトは、servicesオブジェクトを使用して新しい従業員を作成し、その名前を戻します。
- 戻された名前は、予期された名前
John Doe
と等しいとアサートされます。 - servicesオブジェクトで
exitVM
メソッドをコールする2番目のスクリプトが評価されます。これは、exitVMメソッドがゲスト・アプリケーションに公開されていないためPolyglotException
で失敗します。
ホスト・アクセスは、カスタムHostAccess
ポリシーを作成することで完全にカスタマイズできます。
ホスト・コールバック・パラメータのスコープ指定の制御
デフォルトでは、Value
の有効期間は、対応するContext
と同じです。ただし、このデフォルトの動作を変更し、値をスコープにバインドすることが望ましい場合もあります。そうすると、実行がスコープから外れたときに、値が無効になります。このようなスコープの例には、Value
がコールバック・パラメータとして渡される、ゲストからホストへのコールバックがあります。コールバック・パラメータの受渡しがデフォルトのHostAccess.EXPLICIT
でどのように動作するかは前述のとおりです:
public class Services {
Value lastResult;
@HostAccess.Export
public void callback(Value result) {
this.lastResult = result;
}
String getResult() {
return this.lastResult.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.EXPLICIT).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS');");
System.out.println(s.getResult());
}
}
この例では、lastResult
がゲストの値(ホスト上に格納される)への参照を保持し、callback()
のスコープが終了した後もアクセス可能な状態を保ちます。
ただし、これが常に望ましいとはかぎりません。値を有効にしておくと、リソースを不必要にブロックする可能性または一時的な値の動作を正確に反映しない可能性があるためです。このようなケースでは、HostAccess.SCOPED
を使用できます。これは、すべてのコールバックのデフォルト動作を変更して、コールバック・パラメータとして渡される値がコールバックの期間中のみ有効になるようにします。
前述のコードをHostAccess.SCOPED
を使用して動作させるには、コールバック・パラメータとして渡される個々の値を固定して、それらの有効性をコールバックが戻るまで延長します:
public class Services {
Value lastResult;
@HostAccess.Export
void callback(Value result, Value notneeded) {
this.lastResult = result;
this.lastResult.pin();
}
String getResult() {
return this.lastResult.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS', 'foobar');");
System.out.println(services.getResult());
}
}
または、@HostAccess.DisableMethodScope
の注釈がある場合に、コールバック・メソッド全体でスコープ指定をオプトアウトして、コールバックのすべてのパラメータの通常のセマンティクスを維持します:
public class Services {
Value lastResult;
Value metaInfo;
@HostAccess.Export
@HostAccess.DisableMethodScope
void callback(Value result, Value metaInfo) {
this.lastResult = result;
this.metaInfo = metaInfo;
}
String getResult() {
return this.lastResult.asString() + this.metaInfo.asString();
}
}
public static void main(String[] args) {
Services s = new Services()
try (Context context = Context.newBuilder().allowHostAccess(HostAccess.SCOPED).build()) {
context.getBindings("js").putMember("services", s);
context.eval("js", "services.callback('Hello from JS', 'foobar');");
System.out.println(services.getResult());
}
}
アクセス権限の構成
ゲスト・アプリケーションに対して、きめ細かいアクセス権限を構成できます。構成は、新しいコンテキストの構築時にContext.Builder
クラスを使用して指定できます。次のアクセス・パラメータを構成できます:
allowPolyglotAccess
を使用して、他の言語へのアクセスを許可します。allowHostAccess
を使用して、ホスト・オブジェクトへのアクセスを許可およびカスタマイズします。allowHostClassLookup
を使用して、ホスト型へのホスト・ルックアップを許可およびカスタマイズします。ゲスト・アプリケーションが、ルックアップ述語によって許可されるホスト・アプリケーション・クラスの検索を許可します。たとえば、ArrayListがclassFilter
によって許可リストに登録され、ホスト・アクセス・ポリシーcontext.eval("js", "var array = Java.type('java.util.ArrayList')")
によってアクセスが許可されている場合、JavascriptコンテキストでJava ArrayListを作成できますallowHostClassLoading
を使用して、ホスト・クラスのロードを許可します。ホスト・アクセス・ポリシーによってクラスへのアクセス権が付与されている場合にのみ、クラスにアクセスできます。allowCreateThread
を使用して、スレッドの作成を許可します。allowNativeAccess
を使用して、ネイティブAPIへのアクセスを許可します。allowIO
を使用してIOへのアクセスを許可し、fileSystem
を使用してプロキシ・ファイル・アクセスを許可します。
ノート: クラスのロード、ネイティブAPIまたはホストI/Oへのアクセス権を付与すると、これらの権限を使用して他のアクセス制限をバイパスできるため、実質的にすべてのアクセス権が付与されます。
ポリグロット・アプリケーションからのネイティブ実行ファイルのビルド
ポリグロット埋込みは、ネイティブ・イメージを使用して事前にコンパイルすることもできます。デフォルトでは、ポリグロットAPIを使用している場合、言語は含まれません。ゲスト言語を有効にするには、--language:<languageId>
(たとえば、--language:js
)オプションを指定する必要があります。このページのすべての例は、native-image
ビルダーを使用してネイティブ実行可能ファイルに変換できます。
次の例は、native-image
を使用して単純なHelloPolyglot JavaScriptアプリケーションをビルドする方法を示しています。
javac HelloPolyglot.java
native-image --language:js -cp . HelloPolyglot
./hellopolyglot
一部の言語(Python、Rubyなど)は、制限なしで機能するためにその言語のホーム・ディレクトリが必要です。多言語アプリケーションがJVM上で実行されている場合(こちらなど)、言語ホームが自動的に検出されます。ただし、ネイティブ・イメージの場合は、言語ホームを指定する必要があります。現在取り組んでいる機能によって、native-image
ビルダーが、必要な言語ホームを生成された実行可能ファイル/ライブラリと同じディレクトリに自動的にバンドルできるようになる予定です。この機能が提供されるまでは、実行時にオプション-Dorg.graalvm.home=$GRAALVM_HOME
を使用してGraalVMホームを指定することをお薦めします(環境変数GRAALVM_HOME
にはGraalVMホーム・ディレクトリの絶対パスを設定します)。言語ホームが、指定したディレクトリで自動的に検出されます。たとえば:
native-image --language:python -cp . HelloPolyglot
./hellopolyglot -Dorg.graalvm.home=$GRAALVM_HOME
ノート: 実行時にホームを指定するGraalVMのバージョンは、ネイティブの実行可能ファイル/ライブラリのビルドに使用されるGraalVMのバージョンと一致する必要があります。
JITコンパイラの除外
-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime
オプションをビルダーに渡して、ゲスト言語をネイティブ実行可能ファイルに含め、JITコンパイラを除外することができます。フラグ-Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime
は、デフォルト設定をオーバーライドするように、すべてのTruffle言語/ツール・オプションの後に配置する必要があります。
次の例は、Truffle言語インタプリタのみを含むイメージを作成するネイティブ・イメージ・ビルド・コマンドを示しています(Graalコンパイラはイメージに含まれません)。
native-image --language:js -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime -cp . HelloPolyglotInterpreter
ネイティブ・ホスト・リフレクションの構成
ゲスト・アプリケーションからホストJavaコードへのアクセスが機能するには、Javaリフレクションが必要です。ネイティブ実行可能ファイル内でリフレクションを使用する場合は、リフレクション構成ファイルが必要です。
この例では、JavaScriptを使用して、ネイティブの実行可能ファイルによるホスト・アクセスを示します。次のコードをAccessJavaFromJS.java
という名前の新しいファイルにコピーします。
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.proxy.*;
import java.util.concurrent.*;
public class AccessJavaFromJS {
public static class MyClass {
public int id = 42;
public String text = "42";
public int[] arr = new int[]{1, 42, 3};
public Callable<Integer> ret42 = () -> 42;
}
public static void main(String[] args) {
try (Context context = Context.newBuilder()
.allowAllAccess(true)
.build()) {
context.getBindings("js").putMember("javaObj", new MyClass());
boolean valid = context.eval("js",
" javaObj.id == 42" +
" && javaObj.text == '42'" +
" && javaObj.arr[1] == 42" +
" && javaObj.ret42() == 42")
.asBoolean();
System.out.println("Valid " + valid);
}
}
}
次のコードをreflect.json
にコピーします:
[
{ "name": "AccessJavaFromJS$MyClass", "allPublicFields": true },
{ "name": "java.util.concurrent.Callable", "allPublicMethods": true }
]
これで、ホスト・アクセスをサポートするネイティブの実行可能ファイルを作成できます:
javac AccessJavaFromJS.java
native-image --language:js -H:ReflectionConfigurationFiles=reflect.json -cp . AccessJavaFromJS
./accessjavafromjs
イメージでアサーションが必要な場合は、-H:+RuntimeAssertions
オプションをnative-image
に渡すことができます。本番デプロイメントでは、このオプションは省略してください。
複数のコンテキストにわたるコード・キャッシング
GraalVMポリグロットAPIでは、複数のコンテキストにわたるコード・キャッシングが可能です。コード・キャッシングを使用すると、コンパイルされたコードを再利用でき、ソースの解析を1回のみにできます。コード・キャッシングにより、多くの場合、アプリケーションのメモリー消費およびウォームアップ時間を削減できます。
デフォルトでは、コードは単一のコンテキスト・インスタンス内でのみキャッシュされます。複数のコンテキスト間でのコード・キャッシングを有効にするには、明示的なエンジンを指定する必要があります。エンジンは、コンテキスト・ビルダーを使用してコンテキストを作成するときに指定します。コード共有のスコープは、エンジン・インスタンスによって決定されます。コードは、1つのエンジン・インスタンスに関連付けられたコンテキスト間でのみ共有されます。
すべてのソースがデフォルトでキャッシュされます。キャッシングは、cached(boolean cached)をfalse
に設定することで明示的に無効にできます。キャッシングの無効化は、ソースが1回のみ評価されることがわかっている場合に役立つことがあります。
例として、次のコード・スニペットを考えてみます:
public class Main {
public static void main(String[] args) {
try (Engine engine = Engine.create()) {
Source source = Source.create("js", "21 + 21");
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
}
}
}
このコードでは:
import org.graalvm.polyglot.*
で、ポリグロットAPIのベースAPIをインポートします。Engine.create()
で、デフォルト構成で新しいエンジン・インスタンスを作成します。Source.create()
では、JavaScriptの言語識別子である"js"言語を使用して、式"21 + 21"のソース・オブジェクトを作成します。Context.newBuilder().engine(engine).build()
では、明示的なエンジンが割り当てられた新しいコンテキストを構築します。1つのエンジンに関連付けられているすべてのコンテキストでコードが共有されます。context.eval(source).asInt()
は、ソースを評価し、結果をValue
インスタンスとして戻します。
ゲスト言語に埋め込まれたゲスト言語
GraalVM Polyglot APIは、Javaの相互運用性を利用して、ゲスト言語内から使用できます。これは、スクリプトを親コンテキストから分離して実行する必要がある場合に役立ちます。ホスト言語としてのJavaでは、Context.eval(Source)
へのコールはValue
のインスタンスを返しますが、このコードをゲスト言語の一部として実行しているため、かわりに言語固有の相互運用性APIを使用できます。そのため、言語の標準値など、言語内に作成されたコンテキストによって返される値を使用できます。次の例では、value.getMember("data")
ではなくvalue.data
を簡便に記述できます。外部値と相互運用する方法の詳細は、各言語のドキュメントを参照してください。複数のコンテキスト間での値の共有の詳細は、こちらを参照してください。
例として、次のコード・スニペットを考えてみます:
import org.graalvm.polyglot.*;
public class Main {
public static void main(String[] args) {
try (Context outer = Context.newBuilder()
.allowAllAccess(true)
.build()) {
outer.eval("js", "inner = Java.type('org.graalvm.polyglot.Context').create()");
outer.eval("js", "value = inner.eval('js', '({data:42})')");
int result = outer.eval("js", "value.data").asInt();
outer.eval("js", "inner.close()");
System.out.println("Valid " + (result == 42));
}
}
}
このコードでは:
Context.newBuilder().allowAllAccess(true).build()
は、すべての権限を持つ新しい外部コンテキストを構築します。outer.eval
は、外部コンテキストでJavaScriptスニペットを評価します。- 最初のJSスクリプト行
inner = Java.type('org.graalvm.polyglot.Context').create()
は、Javaホスト・タイプのコンテキストを検索し、権限のない新しい内部コンテキスト・インスタンスを作成します(デフォルト)。 inner.eval('js', '({data:42})');
は、内部コンテキストでJavaScriptコード({data:42})
を評価し、結果を格納します。"value.data"
この行は、内部コンテキストの結果からメンバーdata
を読み取ります。この結果は、内部コンテキストがまだクローズされていない間のみ読み取ることができることに注意してください。context.eval("js", "c.close()")
このスニペットは、内部コンテキストを閉じます。内部コンテキストは手動でクローズする必要があり、親コンテキストで自動的にはクローズされません。- 最後に、この例では
Valid true
がコンソールに出力されることが予想されます。
多数の言語用のシェルのビルド
GraalVMポリグロットAPIでは、数行のコードを使用して、GraalVMでサポートされている任意のゲスト言語と統合するアプリケーションをビルドできます。
このシェル実装は、特定のゲスト言語に依存しません。
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
PrintStream output = System.out;
Context context = Context.newBuilder().allowAllAccess(true).build();
Set<String> languages = context.getEngine().getLanguages().keySet();
output.println("Shell for " + languages + ":");
String language = languages.iterator().next();
for (;;) {
try {
output.print(language + "> ");
String line = input.readLine();
if (line == null) {
break;
} else if (languages.contains(line)) {
language = line;
continue;
}
Source source = Source.newBuilder(language, line, "<shell>")
.interactive(true).buildLiteral();
context.eval(source);
} catch (PolyglotException t) {
if(t.isExit()) {
break;
}
t.printStackTrace();
}
}
実行リスナーを使用したステップ実行
GraalVMポリグロットAPIでは、ユーザーはExecutionListenerクラスを使用してゲスト言語の実行をインストゥルメントできます。たとえば、ゲスト言語プログラムのすべての文に対して起動される実行リスナーをアタッチできます。実行リスナーはポリグロットの埋込み機能用の単純なAPIとして設計され、プログラムのシングルステップ実行などに役立つ場合があります。
import org.graalvm.polyglot.*;
import org.graalvm.polyglot.management.*;
public class ExecutionListenerTest {
public static void main(String[] args) {
try (Context context = Context.create("js")) {
ExecutionListener listener = ExecutionListener.newBuilder()
.onEnter((e) -> System.out.println(
e.getLocation().getCharacters()))
.statements(true)
.attach(context.getEngine());
context.eval("js", "for (var i = 0; i < 2; i++);");
listener.close();
}
}
}
このコードでは:
Context.create()
コールで、ゲスト言語の新しいコンテキストを作成します。ExecutionListeners.newBuilder()
を起動して、実行リスナー・ビルダーを作成します。- 要素の実行が開始および消費されたときに通知するために、
onEnter
イベントを設定します。少なくとも1つのイベント・コンシューマと1つのフィルタ処理されたソース要素を有効にする必要があります。 - リスナーのアタッチを完了するには、
attach()
を起動する必要があります。 statements(true)
は、実行リスナーを文のみにフィルタ処理します。context.eval()
コールで、ゲスト言語コードの指定されたスニペットを評価します。listener.close()
によってリスナーが早期にクローズしますが、実行リスナーはエンジンとともに自動的にクローズします。
ポリグロット分離
GraalVM Enterpriseでは、専用のnative-image
分離で実行するようにポリグロット・エンジンを構成できます。この試験段階の機能は、--engine.SpawnIsolate
オプションによって有効になります。このモードで実行されているエンジンは、独自のガベージ・コレクタおよびJITコンパイラを使用して、VMレベルのフォルト・ドメイン内で実行されます。エンジンが分離内で実行されるということは、ポリグロットAPIと相互運用性に関して完全に透過的です:
import org.graalvm.polyglot.*;
public class PolyglotIsolate {
public static void main(String[] args) {
Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.SCOPED)
.allowExperimentalOptions(true)
.option("engine.SpawnIsolate", "true").build();
Value function = context.eval("js", "x => x+1")
assert function.canExecute();
int x = function.execute(41).asInt();
assert x == 42;
}
}
ホストのGCと分離のGCは互いに認識していないため、両方のヒープ上のオブジェクト間で循環参照が発生する可能性があります。したがって、循環参照を回避するには、ホスト・コールバックのスコープ・パラメータを使用することを強くお薦めします。
エンジンの共有により、同じ分離エンジンで複数のコンテキストを生成できます:
public class PolyglotIsolateMultipleContexts {
public static void main(String[] args) {
try (Engine engine = Engine.newBuilder()
.allowExperimentalOptions(true)
.option("engine.SpawnIsolate", "js").build()) {
Source source = Source.create("js", "21 + 21");
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
try (Context context = Context.newBuilder()
.engine(engine)
.build()) {
int v = context.eval(source).asInt();
assert v == 42;
}
}
}
}
この場合、分離エンジンの言語を--engine.SpawnIsolate
のパラメータとして指定する必要があることに注意してください。その理由は、分離されたエンジンは、どの言語セットが使用可能であるかを認識する必要があるためです。バックグラウンドで、GraalVMは対応するネイティブ・イメージ言語ライブラリを見つけます。単一の言語のみが選択されている場合、その言語のライブラリがロードされます。複数の言語が選択されている場合、GraalVMに付属しているすべてのTruffle言語を含むライブラリlibpolyglot
がロードされます。一致するライブラリが使用できない場合、エンジンの作成は失敗します。
GraalVMの存続期間中にロードできる言語ライブラリは1つのみです。つまり、最初に作成された分離エンジンによって、残りの実行のデフォルトが設定されます: JavaScriptのみを使用する分離エンジンが最初に作成された場合、分離エンジンではJavaScriptのみが使用可能になります。
ヒープ・サイズの設定
ネイティブ・イメージ・ランタイム・オプションの受渡し
分離して実行されているエンジンは、エンジン・ビルダーに--engine.IsolateOption.<option>
を渡すことでネイティブ・イメージ・ランタイム・オプションを使用できます。たとえば、--engine.IsolateOption.MaxHeapSize=128m
を介して分離する最大ヒープ・サイズを設定することで、エンジンで使用される最大ヒープ・メモリーを制限するために使用できます。
import org.graalvm.polyglot.*;
public class PolyglotIsolateMaxHeap {
public static void main(String[] args) {
try {
Context context = Context.newBuilder("js")
.allowHostAccess(HostAccess.SCOPED)
.allowExperimentalOptions(true)
.option("engine.SpawnIsolate", "true")
.option("engine.IsolateOption.MaxHeapSize", "64m").build()
context.eval("js", "var a = [];while (true) {a.push('foobar');}");
} catch (PolyglotException ex) {
if (ex.isResourceExhausted()) {
System.out.println("Resource exhausted");
}
}
}
}
最大ヒープ・サイズを超えると、コンテキストが自動的にクローズされ、PolyglotException
が発生します。
ホスト・コールバック・スタックの余裕の確保
ポリグロット分離では、実験的な--engine.HostCallStackHeadRoom
オプションで、ホスト・コールバックの実行時に保証される最小スタック・サイズが必要になることがあります。使用可能なスタック・サイズが指定したしきい値を下回ると、ホスト・コールバックが失敗します。
メモリー保護
メモリー保護キーをサポートするLinux環境では、実験的な--engine.MemoryProtection=true
オプションを使用して、ハードウェア・レベルでポリグロット分離のヒープを分離できます。このオプションを使用してエンジンを作成すると、分離されたエンジンのヒープに専用の保護キーが割り当てられます。GraalVMによって、エンジンのヒープへのアクセスが有効になるのは、ポリグロット分離のコードの実行時のみです。
依存性の設定
GraalVMの埋込みAPI (つまりorg.graalvm.polyglot.*
)を最大限に使用するには、プロジェクトでGraalVMをJAVA_HOME
として使用する必要があります。それに加えて、graal-sdk.jar
(GraalVMに含まれる)を、プロジェクトに提供された依存性として指定する必要があります。これは主に、プロジェクトがこのAPIを使用するという情報をIDEやその他のツールに提供するためのものです。このMavenの例は、pom.xml
ファイルに次を追加することを意味します。
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>${graalvm.version}</version>
<scope>provided</scope>
</dependency>
また、Javaモジュールを使用する場合、module-info.java
ファイルにはorg.graalvm.sdk
が必要です。
module com.mycompany.app {
requires org.graalvm.sdk;
}