- 適用於 JDK 23 的 GraalVM (最新)
- 適用於 JDK 24 的 GraalVM (搶先體驗)
- 適用於 JDK 21 的 GraalVM
- 適用於 JDK 17 的 GraalVM
- 封存
- 開發組建
常見問題
以下是關於在 GraalVM 上執行 JavaScript 的最常見問題與解答。
相容性 #
GraalJS 與 JavaScript 語言相容嗎? #
GraalJS 與 ECMAScript 2024 規範相容,並隨著 2025 草案規範進一步開發。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,您可能需要將系統屬性
polyglot.js.nashorn-compat
設定為true
,以便使用 Nashorn 相容模式。 - 對於
ant
,當透過 ScriptEngine 使用 GraalJS 時,請使用ANT_OPTS
環境變數 (ANT_OPTS="-Dpolyglot.js.nashorn-compat=true"
)。
為什麼在非 JavaScript 物件(例如來自 Java 的 ProxyArray
)上無法使用 array.map()
或 fn.apply()
等內建函數? #
原因
- 提供給 JavaScript 的 Java 物件會盡可能地視為其 JavaScript 對應物。例如,提供給 JavaScript 的 Java 陣列會盡可能地視為 JavaScript 的陣列外來物件(JavaScript 陣列);函數也是如此。一個明顯的差異是此類物件的原型是
null
。這表示雖然您可以(例如)在 JavaScript 程式碼中讀取length
或讀取和寫入 Java 陣列的值,但您無法對其呼叫sort()
,因為預設不會提供Array.prototype
。
解決方案
- 雖然物件沒有指定原型的方法,但您可以明確地呼叫它們,例如
Array.prototype.call.sort(myArray)
。 - 我們提供選項
js.foreign-object-prototype
。啟用時,JavaScript 端的物件會取得最適用的原型集(例如Array.prototype
、Function.prototype
、Object.prototype
),因此其行為會更類似於對應類型的原生 JavaScript 物件。此處會套用一般的 JavaScript 優先順序規則,例如,物件本身的屬性(在這種情況下為 Java 物件)會優先於並隱藏原型中的屬性。
請注意,雖然可以對各自的 Java 類型呼叫 JavaScript 內建函數(例如來自 Array.prototype
的函數),但這些函數預期的是 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
字尾)。預設的原生模式提供更快的啟動速度和更低的延遲,但一旦應用程式預熱,其峰值效能可能會較慢 (吞吐量較低)。在 JVM 模式中,您的應用程式可能需要多數百毫秒才能啟動,但通常會展現更好的峰值效能。 - 儘管每次都執行相同的程式碼,但透過新建立的
org.graalvm.polyglot.Context
重複執行程式碼的速度很慢。
解決方案
- 請在您的基準測試中採用適當的預熱,並忽略應用程式仍在預熱的最初幾次迭代。
- 當將 GraalJS 嵌入 Java 應用程式時,請確保您在 GraalVM JDK 上執行,以獲得最佳效能。
- 使用 JVM 獨立模式,以獲得較慢的啟動速度,但更高的峰值效能。
- 請仔細檢查您是否沒有設定任何可能會降低效能的選項,例如
-ea
/-esa
。 - 當透過
org.graalvm.polyglot.Context
執行程式碼時,請確保共用一個org.graalvm.polyglot.Engine
物件並將其傳遞給每個新建立的Context
。使用org.graalvm.polyglot.Source
物件,並在可能的情況下快取它們。接著,GraalVM 會在 Context 之間共用現有的已編譯程式碼,進而改善效能。如需詳細資訊和範例,請參閱跨多個 Context 的程式碼快取。 - 請嘗試將問題簡化為其根本原因,並提交問題,以便 GraalVM 團隊可以查看。
我如何才能達到最佳峰值效能? #
以下是一些您可以遵循的秘訣,以分析並改善峰值效能
- 測量時,請確保您已讓 Graal 編譯器有足夠的時間來編譯所有熱門方法,然後才開始測量峰值效能。一個有用的命令列選項是
--engine.TraceCompilation=true
—這會在編譯 (JavaScript) 方法時輸出訊息。請勿開始您的測量,直到此訊息變得不那麼頻繁。 - 如果可能,請比較原生映像和 JVM 模式之間的效能。根據您的應用程式的特性,其中一種模式可能會顯示較佳的峰值效能。
- Polyglot API 隨附數個工具和選項,可檢查您的應用程式的效能
- 當應用程式終止時,
--cpusampler
和--cputracer
會列印最熱門的方法清單。使用該清單來找出您的應用程式中大部分時間花費的位置。 --experimental-options --memtracer
可以協助您了解應用程式的記憶體配置。如需更多詳細資訊,請參閱分析命令列工具。
- 當應用程式終止時,
在原生映像中執行 GraalJS 與在 JVM 中執行之間有何差異? #
基本上,GraalJS 引擎是一個簡單的 Java 應用程式。可以在任何 JVM (JDK 21 或更新版本) 上執行它,但是為了獲得更好的結果,它應該是 GraalVM JDK,或使用 Graal 編譯器的相容 Oracle JDK。此模式可讓 JavaScript 引擎在執行階段完全存取 Java,但也需要 JVM 首先 (即時) 編譯 JavaScript 引擎(在執行時),就像任何其他 Java 應用程式一樣。
在原生映像中執行表示 JavaScript 引擎(包括其來自 JDK 等的所有相依性)會預先編譯為原生可執行檔。這將大幅減少任何 JavaScript 應用程式的啟動時間,因為 GraalVM 可以立即開始編譯 JavaScript 程式碼,而無需先自行編譯。然而,此模式只會讓 GraalVM 存取映像建立時已知的 Java 類別。最重要的是,這表示 JavaScript 至 Java 的互通性功能在此模式下不可用,因為它們需要在執行階段動態載入類別並執行任意 Java 程式碼。
錯誤 #
TypeError:無法存取主機類別 com.myexample.MyClass,或該類別不存在 #
原因
- 您嘗試存取的 Java 類別對於
js
程序來說是未知的,或者不在您程式碼可存取的允許類別之中。
解決方案
- 請確保類別名稱沒有拼寫錯誤。
- 請確保該類別位於類別路徑中。請使用
--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
原因
- 在某些情況下,從 JavaScript 呼叫 Java 時,GraalJS 不允許具體的 callback 類型。例如,一個期望
Value
物件的 Java 函式,可能會因此而產生引用的錯誤訊息。
解決方案
- 請更改 Java callback 方法中的簽名。
狀態
- 這是一個已知限制,應在未來版本中解決。
範例
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.
原因
- 您嘗試對一個 polyglot 物件執行該物件不處理的操作(訊息)。例如,您在一個不可執行的物件上呼叫
Value.execute()
。 - 安全性設定(例如,
org.graalvm.polyglot.HostAccess
)可能會阻止該操作。
解決方案
- 請確保相關的物件(類型)確實處理該訊息。
- 具體來說,請確保您嘗試在 Java 類型上執行的 JavaScript 操作在語義上在 Java 中是可行的。例如,雖然您可以在 JavaScript 中將值
push
到陣列並自動增長陣列,但 Java 中的陣列是固定長度的,嘗試 push 到 Java 陣列將導致Message not supported
錯誤。在這種情況下,您可能需要包裝 Java 物件,例如作為ProxyArray
。 - 請確保允許存取該類別,方法是在您的類別上加上
@HostAccess.Export
註解,和/或將Context.Builder.allowHostAccess()
設定為寬鬆的設定。請參閱 org.graalvm.polyglot.Context。 - 您是否嘗試呼叫 Java Lambda 運算式或函數介面?使用
@HostAccess.Export
註解正確的方法可能是一個陷阱。雖然您可以註解函數介面所參考的方法,但介面本身(或在背景建立的 Lambda 類別)無法正確註解並被識別為 *exported*。請參閱以下範例,其中強調了問題和可行的解決方案。
以下範例會在某些 HostAccess
設定下觸發 Message not supported
錯誤,例如 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
,而不是在產生的 lambda 類別中的可執行函式。
為了將 @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 編譯器的相容 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 或參閱 如何在 Stock JDK 上執行 GraalJS 指南,以了解如何在相容的啟用 Graal 的 stock JDK 上設定 Graal 編譯器。
不過,如果這是故意的,您可以使用上述選項停用警告,並繼續以效能降低的方式執行,可以透過命令列或使用 Context.Builder
來設定,例如
try (Context ctx = Context.newBuilder("js")
.option("engine.WarnInterpreterOnly", "false")
.build()) {
ctx.eval("js", "console.log('Greetings!');");
}
請注意,當使用明確的 polyglot 引擎時,必須在 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!');");
}
}