- JDK 23対応 GraalVM (最新版)
- JDK 24対応 GraalVM (早期アクセス版)
- JDK 21対応 GraalVM
- JDK 17対応 GraalVM
- アーカイブ
- 開発ビルド
よくある質問
GraalVMで実行されるJavaScriptに関するよくある質問と回答を以下に示します。
互換性 #
GraalJSはJavaScript言語と互換性がありますか? #
GraalJSはECMAScript 2024仕様と互換性があり、さらに2025ドラフト仕様 alongside で開発が進められています。GraalJSの互換性は、Kangax ECMAScript互換性テーブルなどの外部ソースによって検証されています。
GraalJSは、ECMAScriptの公式テストスイートであるtest262や、V8とNashornによって公開されているテスト、Node.jsユニットテスト、GraalJS独自のユニットテストなど、一連のテストエンジンに対してテストされています。
GraalVMがサポートするJavaScript APIを説明するリファレンスドキュメントについては、GRAAL.JS-APIを参照してください。
私のアプリケーションはNashornで動作していましたが、GraalJSでは動作しません。なぜですか? #
理由
- GraalJSは、ECMAScript仕様と、競合するエンジン(Nashornを含む)との互換性を保つように努めています。場合によっては、これは矛盾する要件となります。このような場合、ECMAScriptが優先されます。また、GraalJSがNashornの機能を意図的に正確に複製しない場合もあります(たとえば、セキュリティ上の理由から)。
解決策
- GraalJSのNashorn互換モードを有効にして、デフォルトでは有効になっていない機能を追加します。これでほとんどの場合が解決するはずです。ただし、これはアプリケーションのセキュリティに悪影響を与える可能性があることに注意してください。詳細については、Nashorn移行ガイドを参照してください。
具体的なアプリケーション
- JSR 223 ScriptEngineの場合、Nashorn互換モードを使用するために、システムプロパティ`polyglot.js.nashorn-compat`を`true`に設定することをお勧めします。
- `ant`の場合、ScriptEngine経由でGraalJSを使用する際に、`ANT_OPTS`環境変数(`ANT_OPTS="-Dpolyglot.js.nashorn-compat=true"`)を使用します。
Javaからの`ProxyArray`などの非JavaScriptオブジェクトで、`array.map()`や`fn.apply()`などの組み込み関数が使用できないのはなぜですか? #
理由
- JavaScriptに提供されるJavaオブジェクトは、できる限りJavaScriptの対応するものと同様に扱われます。たとえば、JavaScriptに提供されるJava配列は、可能な限りJavaScriptの*配列エキゾチックオブジェクト*(JavaScript配列)のように扱われます。*関数*についても同様です。明らかな違いの1つは、そのようなオブジェクトのプロトタイプが`null`であることです。これは、たとえば、JavaScriptコードでJava配列の`length`を読み取ったり、値を読み書きしたりすることはできますが、`Array.prototype`がデフォルトで提供されていないため、`sort()`を呼び出すことはできないことを意味します。
解決策
- オブジェクトにはプロトタイプのメソッドが割り当てられていませんが、`Array.prototype.call.sort(myArray)`のように明示的に呼び出すことができます。
- 私たちは`js.foreign-object-prototype`オプションを提供しています。有効にすると、JavaScript側のオブジェクトには、最も適切なプロトタイプセット(`Array.prototype`、`Function.prototype`、`Object.prototype`など)が設定され、それぞれのタイプのネイティブJavaScriptオブジェクトと同様に動作できます。通常のJavaScriptの優先順位ルールがここに適用されます。たとえば、オブジェクトの own プロパティ(その場合はJavaオブジェクト)は、プロトタイプのプロパティよりも優先され、非表示にします。
`Array.prototype`などのJavaScript組み込み関数をそれぞれのJava型で呼び出すことはできますが、これらの関数はJavaScriptのセマンティクスを想定していることに注意してください。これは、Javaで操作がサポートされていない場合、操作が失敗する可能性があることを意味します(通常は`TypeError`:`Message not supported`)。`Array.prototype.push`を例として考えてみましょう。JavaScriptでは配列のサイズを増やすことができますが、Javaでは配列のサイズは固定されているため、値をプッシュすることはセマンティクス的に不可能であり、失敗します。このような場合は、Javaオブジェクトをラップして、そのケースを明示的に処理できます。その目的のために、インターフェース`ProxyObject`と`ProxyArray`を使用してください。
GraalJSが自分のアプリケーションで動作することをどのように確認できますか? #
モジュールにテストが付属している場合は、GraalJSで実行してください。もちろん、これはアプリケーションのみをテストし、依存関係はテストしません。GraalVM言語互換性ツールを使用して、対象のモジュールがGraalJSでテストされているかどうか、およびテストが正常に合格するかどうかを確認できます。さらに、`package-lock.json`または`package.json`ファイルをそのツールにアップロードすると、すべての依存関係が分析されます。
パフォーマンス #
GraalJSでのアプリケーションのパフォーマンスが他のエンジンよりも遅いのはなぜですか? #
理由
- ベンチマークでウォームアップが考慮されていることを確認してください。最初の数回の反復では、GraalJSは他のエンジンよりも遅くなる可能性がありますが、十分なウォームアップの後、この違いは解消されるはずです。
- GraalJSは、ネイティブ(デフォルト)とJVM(`-jvm`接尾辞付き)の2つの異なるスタンドアロンで提供されます。デフォルトの*ネイティブ*モードは、高速な起動と低レイテンシを提供しますが、アプリケーションがウォームアップされると、ピークパフォーマンス(スループット)が低下する可能性があります。*JVM*モードでは、アプリケーションの起動に数百ミリ秒余計にかかる場合がありますが、通常はピークパフォーマンスが向上します。
- 新しく作成された`org.graalvm.polyglot.Context`を介したコードの繰り返し実行は、毎回同じコードが実行されているにもかかわらず、遅くなります。
解決策
- ベンチマークで適切なウォームアップを使用し、アプリケーションがまだウォームアップされている最初の数回の反復は無視してください。
- JavaアプリケーションにGraalJSを埋め込む場合は、最高のパフォーマンスを得るためにGraalVM JDKで実行していることを確認してください。
- 起動は遅いですが、ピークパフォーマンスの高いJVMスタンドアロンを使用してください。
- `-ea` / `-esa`など、パフォーマンスを低下させる可能性のあるオプションが設定されていないことを再確認してください。
- `org.graalvm.polyglot.Context`を介してコードを実行する場合は、1つの`org.graalvm.polyglot.Engine`オブジェクトが共有され、新しく作成された各`Context`に渡されることを確認してください。`org.graalvm.polyglot.Source`オブジェクトを使用し、可能な場合はキャッシュしてください。その後、GraalVMはコンテキスト間で既存のコンパイル済みコードを共有し、パフォーマンスを向上させます。詳細と例については、複数のコンテキストにわたるコードキャッシュを参照してください。
- 問題の根本原因を突き止め、問題を報告して、GraalVMチームが確認できるようにしてください。
最高のピークパフォーマンスを達成するにはどうすればよいですか? #
ピークパフォーマンスを分析および向上させるために従うことができるヒントをいくつか紹介します
- 測定する場合は、ピークパフォーマンスの測定を開始する前に、Graalコンパイラーにすべてのホットメソッドをコンパイルするのに十分な時間を与えていることを確認してください。 daarvoor の便利なコマンドラインオプションは`--engine.TraceCompilation=true`です。これは、(JavaScript)メソッドがコンパイルされるたびにメッセージを出力します。このメッセージの頻度が低下するまで、測定を開始しないでください。
- 可能であれば、ネイティブイメージとJVMモードのパフォーマンスを比較してください。アプリケーションの特性に応じて、どちらか一方がピークパフォーマンスを示す場合があります。
- Polyglot APIには、アプリケーションのパフォーマンスを検査するためのいくつかのツールとオプションが付属しています
- `--cpusampler`と`--cputracer`は、アプリケーションが終了したときに最もホットなメソッドのリストを出力します。そのリストを使用して、アプリケーションのどこで最も多くの時間が費やされているかを把握します。
- `--experimental-options --memtracer`は、アプリケーションのメモリ割り当てを理解するのに役立ちます。詳細については、プロファイリングコマンドラインツールを参照してください。
JVMと比較して、ネイティブイメージでGraalJSを実行することの違いは何ですか? #
本質的に、GraalJSエンジンはプレーンなJavaアプリケーションです。任意のJVM(JDK 21以降)で実行することは可能ですが、より良い結果を得るためには、GraalVM JDK、またはGraalコンパイラを使用する互換性のあるOracle JDKである必要があります。このモードでは、JavaScriptエンジンは実行時にJavaに完全にアクセスできますが、他のJavaアプリケーションと同様に、JVMが最初に(ジャストインタイムで)JavaScriptエンジンをコンパイルする必要があります。
ネイティブイメージで実行するということは、JavaScriptエンジン(JDKからのすべての依存関係を含む)がネイティブ実行可能ファイルにプリコンパイルされることを意味します。GraalVMは、最初にコンパイルする必要なく、JavaScriptコードのコンパイルをすぐに開始できるため、JavaScriptアプリケーションの起動が大幅に短縮されます。ただし、このモードでは、GraalVMはイメージ作成時に認識されているJavaクラスにのみアクセスできます。最も重要なことは、JavaScriptとJavaの相互運用機能は、実行時に動的なクラスのロードと任意のJavaコードの実行が必要になるため、このモードでは使用できないことを意味します。
エラー #
TypeError:ホストクラスcom.myexample.MyClassへのアクセスは許可されていないか、存在しません #
理由
- `js`プロセスに認識されていないJavaクラス、またはコードがアクセスできる許可されたクラスに含まれていないJavaクラスにアクセスしようとしています。
解決策
- クラス名にタイプミスがないことを確認してください。
- クラスがクラスパス上にあることを確認してください。`--vm.cp=<classpath>`オプションを使用します。
- クラスに`@HostAccess.Export`アノテーションを付け、/または`Context.Builder.allowHostAccess()`を許可設定に設定することにより、クラスへのアクセスが許可されていることを確認してください。org.graalvm.polyglot.Contextを参照してください。
TypeError:UnsupportedTypeException #
TypeError: execute on JavaObject[Main$$Lambda$63/1898325501@1be2019a (Main$$Lambda$63/1898325501)] failed due to: UnsupportedTypeException
理由
- GraalJS は、JavaScript から Java を呼び出す際に、具体的なコールバック型を許可しない場合があります。たとえば、
Value
オブジェクトを予期する Java 関数は、このため、引用されたエラーメッセージで失敗する可能性があります。
解決策
- Java コールバックメソッドのシグネチャを変更します。
ステータス
- これは既知の制限であり、将来のバージョンで解決される予定です。
例
import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.HostAccess;
public class Minified {
public static void main(String ... args) {
//change signature to Function<Object, String> to make it work
Function<Value, String> javaCallback = (test) -> {
return "passed";
};
try(Context ctx = Context.newBuilder()
.allowHostAccess(HostAccess.ALL)
.build()) {
Value jsFn = ctx.eval("js", "f => function() { return f(arguments); }");
Value javaFn = jsFn.execute(javaCallback);
System.out.println("finished: "+javaFn.execute());
}
}
}
TypeError: メッセージがサポートされていません #
TypeError: execute on JavaObject[Main$$Lambda$62/953082513@4c60d6e9 (Main$$Lambda$62/953082513)] failed due to: Message not supported.
理由
- このオブジェクトが処理しないポリグロットオブジェクトに対して操作(メッセージ)を実行しようとしています。たとえば、実行不可能なオブジェクトに対して
Value.execute()
を呼び出しています。 - セキュリティ設定(たとえば、
org.graalvm.polyglot.HostAccess
)によって、操作が妨げられる場合があります。
解決策
- 問題のオブジェクト(型)がそれぞれのメッセージを処理することを確認してください。
- 具体的には、Java 型で実行しようとしている JavaScript 操作が、Java で意味的に可能であることを確認してください。たとえば、JavaScript で配列に値を
push
して配列を自動的に拡張できますが、Java の配列は長さが固定されているため、Java 配列にプッシュしようとすると、メッセージがサポートされていません
というエラーが発生します。このような場合は、Java オブジェクトをProxyArray
などにラップすることをお勧めします。 - クラスに`@HostAccess.Export`アノテーションを付け、/または`Context.Builder.allowHostAccess()`を許可設定に設定することにより、クラスへのアクセスが許可されていることを確認してください。org.graalvm.polyglot.Contextを参照してください。
- Java ラムダ式または関数型インターフェースを呼び出そうとしていますか?適切なメソッドに
@HostAccess.Export
アノテーションを付けることは、落とし穴になる可能性があります。関数型インターフェースが参照するメソッドにアノテーションを付けることはできますが、インターフェース自体(またはバックグラウンドで作成されたラムダクラス)は、適切にアノテーションが付けられず、*エクスポート*済みとして認識されません。問題と有効な解決策を強調した例については、以下を参照してください。
特定の HostAccess
設定(例:HostAccess.EXPLICIT
)で メッセージがサポートされていません
エラーをトリガーする例
{
...
//a JS function expecting a function as argument
Value jsFn = ...;
//called with a functional interface as argument
jsFn.execute((Function<Integer, Integer>)this::javaFn);
...
}
@Export
public Object javaFn(Object x) { ... }
@Export
public Callable<Integer> lambda42 = () -> 42;
上記の例では、メソッド javaFn
には一見 @Export
のアノテーションが付けられていますが、jsFn
に渡される関数型インターフェースにはアノテーションが付けられていません。これは、関数型インターフェースが javaFn
のラッパーのように動作し、アノテーションを隠しているためです。lambda42
も適切にアノテーションが付けられていません。このパターンは、*フィールド* lambda42
にアノテーションを付けており、生成されたラムダクラスの実行可能関数にはアノテーションを付けていません。
関数型インターフェースに @Export
アノテーションを追加するには、代わりにこのパターンを使用します
import java.util.function.Function;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.HostAccess;
public class FAQ {
public static void main(String[] args) {
try(Context ctx = Context.newBuilder()
.allowHostAccess(HostAccess.EXPLICIT)
.build()) {
Value jsFn = ctx.eval("js", "f => function() { return f(arguments); }");
Value javaFn = jsFn.execute(new MyExportedFunction());
System.out.println("finished: " + javaFn.execute());
}
}
@FunctionalInterface
public static class MyExportedFunction implements Function<Object, String> {
@Override
@HostAccess.Export
public String apply(Object s) {
return "passed";
}
};
}
別の選択肢は、java.function.Function
の apply
メソッドへのアクセスを許可することです。ただし、これにより、このインターフェースの*すべて*のインスタンスへのアクセスが許可されることに注意してください。ほとんどの本番環境では、これは許可が行き過ぎており、潜在的なセキュリティホールが開く可能性があります。
HostAccess ha = HostAccess.newBuilder(HostAccess.EXPLICIT)
//warning: too permissive for use in production
.allowAccess(Function.class.getMethod("apply", Object.class))
.build();
警告:実装はランタイムコンパイルをサポートしていません。 #
次の警告が表示される場合は、GraalVM JDK、または Graal Compiler を使用する互換性のある Oracle JDK または OpenJDK で実行されていません
[engine] WARNING: The polyglot context is using an implementation that does not support runtime compilation.
The guest application code will therefore be executed in interpreted mode only.
Execution only in interpreted mode will strongly impair guest application performance.
To disable this warning, use the '--engine.WarnInterpreterOnly=false' option or the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.
これを解決するには、GraalVM を使用するか、互換性のある Graal 対応の標準 JDK に Graal コンパイラをセットアップする方法については、標準 JDK で GraalJS を実行するためのガイドを参照してください。
それでも、これが意図的な場合は、上記のオプションをコマンドラインまたは Context.Builder
を使用して設定することにより、警告を無効にしてパフォーマンスを低下させたまま実行を続けることができます。例:
try (Context ctx = Context.newBuilder("js")
.option("engine.WarnInterpreterOnly", "false")
.build()) {
ctx.eval("js", "console.log('Greetings!');");
}
明示的なポリグロットエンジンを使用する場合は、オプションを Engine
に設定する必要があることに注意してください。例:
try (Engine engine = Engine.newBuilder()
.option("engine.WarnInterpreterOnly", "false")
.build()) {
try (Context ctx = Context.newBuilder("js").engine(engine).build()) {
ctx.eval("js", "console.log('Greetings!');");
}
}