よくある質問

次に、GraalVMで実行されているJavaScriptに関して最もよくある質問とその回答を示します。

互換性

GraalVMはJavaScript言語と互換性がありますか。

GraalVMはECMAScript 2022仕様と互換性があり、さらに2023ドラフト仕様に沿って開発されています。GraalVMのJavaScriptランタイムの互換性は、Kangax ECMAScript互換性表などの外部ソースによって検証されています。

GraalVM JavaScriptは、ECMAScriptの公式テスト・スイートであるtest262だけでなく、V8とNashornによって公開されたテスト、Node.jsユニット・テスト、GraalVM独自のユニット・テストなど、一連のテスト・エンジンに対してテストされています。

GraalVMでサポートされているJavaScript APIのリファレンスは、GRAAL.JS-APIを参照してください。

GraalVMは元のノード実装と互換性がありますか。

GraalVMに基づくNode.jsは、(V8エンジンに基づく)元のNode.jsとほぼ互換性があります。これにより、npmベースの多数のモジュールがGraalVMと互換性を持つようになります。実際、テストした100k npmモジュールのうち、94%以上がすべてのテストに合格しています。それでも、いくつかの相違の要因を考慮する必要があります:

GraalVM 21.1以降、Node.jsおよび関連するすべての実行可能ファイル(nodenpmなど)は、デフォルトでGraalVMバイナリに含まれていません。Node.jsサポートは別個のコンポーネントにパッケージ化されています。これは、GraalVMアップデータ$GRAALVM/bin/gu install nodejsを使用してインストールできます。

以前にNashornで実行していたアプリケーションはGraalVM JavaScriptでは動作しないのですか。

理由:

解決策:

特定のアプリケーション:

array.map()fn.apply()などの組込み関数は、JavaのProxyArrayなどのJavaScript以外のオブジェクトでは使用できません

理由:

解決策:

Array.prototypeなどのJavaScript組込み関数は各Java型に対してコールできますが、これらの関数ではJavaScriptセマンティクスが要求されることに注意してください。そのため、たとえば、Javaでサポートされていない操作は失敗する可能性があります(通常はTypeError: Message not supportedが表示されます)。Array.prototype.pushを例として考えます: 配列のサイズはJavaScriptでは大きくなりますが、Javaでは固定であるため、値のプッシュは意味的に不可能であり、失敗します。このような場合は、Javaオブジェクトをラップして、そのケースを明示的に処理できます。そのためには、インタフェースProxyObjectおよびProxyArrayを使用します。

アプリケーションでのGraalVMの動作を検証するにはどうすればよいですか。

モジュールにテストが付属している場合は、それらをGraalVMで実行します。もちろん、テストされるのはアプリケーションのみであり、依存性はテストされません。互換性ツールを使用して、対象のモジュールがGraalVMでテストされているかどうか、またテストに合格したかどうかを確認できます。また、package-lock.jsonまたはpackage.jsonファイルをそのツールにアップロードすると、すべての依存性が一度に分析されます。

パフォーマンス

GraalVM JavaScriptでは別のエンジンよりもアプリケーションの速度が低下します

理由:

解決策:

最高のピーク・パフォーマンスを実現するにはどうすればよいですか。

次に示すいくつかのヒントに従えば、ピーク・パフォーマンスを分析して改善できます:

ネイティブ・イメージでのGraalVMのJavaScriptの実行は、JVMと比較してどのように違いますか。

基本的に、GraalVMのJavaScriptエンジンはプレーンJavaアプリケーションです。任意のJVM (JDK 11以上)で実行できますが、より適切な結果を得るには、GraalVMか、またはGraalVMコンパイラを使用した互換性のあるJVMCI対応のJDKにする必要があります。このモードでは、実行時にJavaScriptエンジンにJavaへの完全なアクセス権が付与されますが、他のJavaアプリケーションと同様に、実行時にJVMで最初(Just-in-Time)にJavaScriptエンジンをコンパイルする必要もあります。

ネイティブ・イメージで実行することは、JavaScriptエンジンがJDKなどのすべての依存性を含めてネイティブ実行可能ファイルにプリコンパイルされることを意味します。これにより、GraalVM自体を最初にコンパイルする必要がなくなり、JavaScriptコードのコンパイルをすぐに開始できるため、JavaScriptアプリケーションの起動が大幅に高速化されます。ただし、このモードでは、イメージ作成時に認識されているJavaクラスへのアクセス権のみがGraalVMに付与されます。この場合、JavaScriptとJavaの相互運用性機能は、実行時に動的なクラスのロードと任意のJavaコードの実行を必要とするため、このモードでは使用できないことに留意してください。

npmパッケージはグローバルにインストールできますか。

ノード・パッケージは、元のNode.js実装とGraalVMの両方で、npmおよび-gオプションを使用してグローバルにインストールできます。

元のNode.js実装には、バイナリおよびグローバルにインストールされたパッケージとそのコマンドライン・ツールを配置するための1つのメイン・フォルダ(NODE/bin)がありますが、GraalVMには、メインのGRAALVM/binフォルダとは別に、言語ごとの個別フォルダ(GRAALVM/jre/languages/js/binなど)がいくつかあります。npmパッケージをGraalVMにグローバルにインストールすると、コマンドライン・インタフェース・ツールなどの実行可能ファイルへのリンクがJavaScript固有のフォルダに配置されます。グローバルにインストールされたパッケージを正しく機能させるには、$PATHGRAALVM/jre/languages/js/binを追加する必要がある場合があります。

もう1つのオプションは、$PREFIX環境変数を設定するか、npm installの実行時に--prefixオプションを指定することで、npmのグローバル・インストール・フォルダを指定することです。

詳細は、npmパッケージのグローバル・インストール」を参照してください。

エラー

TypeError: Access to host class com.myexample.MyClass is not allowed or does not exist

理由:

解決策:

TypeError: UnsupportedTypeException

TypeError: execute on JavaObject[Main$$Lambda$63/1898325501@1be2019a (Main$$Lambda$63/1898325501)] failed due to: UnsupportedTypeException

理由:

解決策:

ステータス:

例:

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: Message not supported

TypeError: execute on JavaObject[Main$$Lambda$62/953082513@4c60d6e9 (Main$$Lambda$62/953082513)] failed due to: Message not supported.

理由:

解決策:

特定のHostAccess設定(HostAccess.EXPLICITなど)を使用してMessage not supportedというエラーをトリガーする例:

{
  ...
  //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";
    }
  };
}

もう1つのオプションは、java.function.Functionapplyメソッドへのアクセスを許可することです。ただし、これにより、このインタフェースのすべてのインスタンスにアクセス可能になることに注意してください。ほとんどの本番設定では、これは許容範囲を超えており、潜在的なセキュリティ・ホールを開くことになります。

HostAccess ha = HostAccess.newBuilder(HostAccess.EXPLICIT)
  //warning: too permissive for use in production
  .allowAccess(Function.class.getMethod("apply", Object.class))
  .build();

警告: 実装は実行時コンパイルをサポートしていません。

次の警告が表示された場合は、GraalVM、またはGraalVMコンパイラを使用するJVMCI対応JVMで実行していません:

[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 impact the guest application performance.
To disable this warning the '--engine.WarnInterpreterOnly=false' option or use the '-Dpolyglot.engine.WarnInterpreterOnly=false' system property.

これを解決するには、GraalVMを使用するか、互換性のあるJVMCI対応ストックJDKでGraalコンパイラを設定する手順についてストックJDKでのGraalVM JavaScriptの実行に関するガイドを参照してください。

ただし、これが意図的である場合は、コマンドラインまたは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!');");
  }
}