In the previous post, we saw the need and history of embedding Javascript in a Java application. Now, let’s see how the latest GraalJS can be used for embedding Javascript in Java applications, how it performs, and how we can further optimize it for better performance – throughput specifically.
GraalVM is the straightforward way to use GraalJS for running Javascript within your Java application. If for some reason, you cannot use GraalVM and want to use GraalJS on stock OpenJDK, you can do so by following the procedure mentioned here.
Running Javascript from Java
Add the following dependencies in your pom.xml, if you’re using Maven as your build tool. Do the equivalent if you’re using Gradle or any other build tool.
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>polyglot</artifactId>
<version>${graalvm.version}</version>
<type>jar</type>
</dependency>
<dependency>
<groupId>org.graalvm.polyglot</groupId>
<artifactId>js</artifactId>
<version>${graalvm.version}</version>
<type>pom</type>
</dependency>
${graalvm.version} is a property pointing to GraalVM version you’re using.
Here is a simple Java code showing how we can run Javascript snippet that adds two integers.
// import org.graalvm.polyglot.Context;
int a = 10;
int b = 10;
int c;
try (Context context = Context.newBuilder().build()) {
context.initialize("js");
context.getBindings("js").putMember("a", a);
context.getBindings("js").putMember("b", b);
c = context.eval("js", "const c = a + b; c").asInt();
System.out.println(String.format("%d + %d = %d", a, b, c));
}
Line 6 – Context is the API that can be used to run guest languages in GraalVM.
Line 7 – Context created is initialized to be used for running Javascript code.
Lines 8 and 9 – Input for the script is bound with the context.
Line 10 – Javascript is evaluated & the result is obtained.
Optimizing performance of Javascript execution in GraalJS
Depending upon the design of Java application & usage of Javascript embedded in it, we may need different kind of performance of Javascript execution from GraalJS.
Interpreter Mode
If the Javascript is to be executed only once, there is no benefit in compiling and then running as that will consume CPU and Memory which is waste. In such cases, you can disable the compilation and run GraalJS in pure interpreter mode as shown below.
Context context = Context
.newBuilder() // Enable Flag to use the Compilation Option
.allowExperimentalOptions(true) // Disable the Compilation
.option("engine.Compilation", "false")
.build();
Compiled Mode
If the Java application application is going to use the same Javascript code multiple times over period of time, it’s recommended to follow some best practices to get good performance.
Since GraalVM’s Context API is not thread-safe, you can choose to create and pool those objects. But, Context is not really an expensive object to be created, if the underlying GraalVM Engine is reused. Let’s see how to create a single GraalVM Engine & reuse it across all your Context objects.
// import org.graalvm.polyglot.Engine;
Engine engine = Engine.newBuilder("js")
.allowExperimentalOptions(true)
.option("engine.Compilation", "true")
.build(); // Create a single Engine instance
Context context = Context
.newBuilder()
// Use the same engine across all your contexts.
.engine(engine)
.build();
To further improve code caching & compilation, Javascript code that is evaluated to be maintained in memory & reused in all contexts. Let’s see how to do that now.
// import org.graalvm.polyglot.Source;
String languageId = "js";
String fileName = "filename.mjs";
String javascriptCode = <load your javascript source into this string>
// Create a single Source instance.
Source source = Source.newBuilder(languageId, javascriptCode, fileName).build();
Context context = Context
.newBuilder()
// Use the same engine across all your contexts.
.engine(engine)
.build();
// Use the source instance for evaluating the script.
context.eval (source);
Using the Source instance with the above specified 3 parameter builder method with filename also comes handy when you want to run Javascript Modules, as the file name ending with .mjs is used by GraalJS to detect the type of Javascript you’re trying to use.
Stay tuned for posts on how to measure performance of GraalJS with interpreter mode and compilation, also how to tune the compilation to optimize usage of CPU and memory.