Create a Native Image Binary Executable for a Polyglot Java Application using GraalVM - using build time class initializing image 144

Create a Native Image Binary Executable for a Polyglot Java Application using GraalVM – using build time class initializing

imageGraalVM provides a runtime component that enhances the JVM in several ways. It makes the JIT compilation better. It also allows the JVM to run non-JVM languages such as JavaScript, R, Python, Ruby and LLVM. And it makes it possible for languages running on the JVM to interact – through the polyglot interoperability. Another valuable capability of GraalVM is called native image generation. This feature allows us to use Ahead of Time Compilation of a Java application and create a standalone native executable image that consists of the code that normally is produced by the JIT compiler at runtime including all Java Runtime components required for running the code. In short: a single executable that can  be run on a vanilla Linux environment [with the operating system for which the executable was produced]. The run time environment does not require a separate, pre installed Java runtime.

In case the Java application is actually a Polyglot application – say one that also executes JavaScript – then the native image produced from the application does not just consist of the Java runtime components (referenced as Substrate VM – a minimal and self-contained runtime) but also needs to include the GraalJS JavaScript execution engine to be able to evaluate JavaScript at runtime. If the JavaScript snippets that are executed by the application are embedded inline in the Java source code, we do not need to do anything extra for making that source code available at runtime. However, if the JavaScript sources are loaded from an external file – we need to find a way to make that file part of the native image (or alternatively pass that file to the binary image at runtime in case it is not included by the GraalVM native image generator).

In this article I show two simple examples of the generation of a native image – one for a polyglot Java application that only had embedded snippets of JavaScript (and possibly Python and R code) and a second one for an application that loads some JavaScript resources from external resources. This application was introduced in great detail in this article. The sources that application are in GitHub: https://github.com/AMIS-Services/graalvm-polyglot-meetup-november2019.

EDIT: 7th November 2019: the original article only described loading the external JavaScript resource at run time (when starting the native image); I have since modified the article to describe the preferred and much more elegant option of using a static initializer in my class to load the JavaScript resource and store the contents in a static String object. By instructing the GraalVM native image tool to initialize this class at image build time, the contents of the external file is made part of the native image

Polyglot Java Application – only embedded polyglot snippets

If the Polyglot Java Application that we want to turn into a native image only used embedded, inline snippets of polyglot code, we can use a straightforward approach. We have to instruct the Native Image generator to include the respective runtimes for each of the languages we want to evaluate snippets of at run time – and the generator will comply. When the binary executable is executed, it has everything it needs to interpret the snippets of ‘polyglot’ code.

For example this application:

imageA Java application that evaluates snippets of code in Python, R and JavaScript.

Our instruction to the GraalVM Native Image producer contains references to each of these languages:

image

<br>
$GRAALVM_HOME/bin/native-image -cp ./application-bundle.jar --language:js --language:python --language:R  --verbose -H:Name=myPolyglotApplication  -H:Class=nl.amis.AppStarter<br>

Note: parameter -H:Name specifies the name of the native image. -H:Class indicates the Java Class whose main method is the entry point to the application. This means that when we run the binary executable, we trigger the native code equivalent of what was in that main method.

Note 2: at first, my native image generation failed with this error: Error: Image build request failed with exit status -1. I believe shortage of memory was the cause here. Producing a native image is quite memory intensive, especially when polyglot  is added. I increased the memory size of my VM from 9GB to 16GB – and that helped me get rid of the error.

The native image that gets produced is a  binary executable that contains the natively executable representation of the Java code as well as the literal snippets of Python, JavaScript and R code and the runtime engines for these three languages and of course Substrate VM, the mini runtime engine.

This application can now be run just like any other executable Linux application: ./myPolyglotApplication

When we invoke the executable, we are not aware of the fact that it was originally created from multiple programming languages and leverages multiple runtime engines.

 

Producing a Native Image for a Polyglot Java that loads Polyglot Resources from External Files

The situation is a little bit more complicated if our application loads some of the Polyglot resources from external files instead of only using embedded, inline snippets. I have created a simple Java application that contains embedded JavaScript snippets and also loads a JavaScript resource (the self contained bundle for the NPM Validator module – as described in this article) at run time. I want to keep the JavaScript code separate from my Java code. The Java application is packaged in a JAR file that contains the JavaScript sources in the root. The Java application reads these resources – using getClass().getResourceAsStream() – at runtime.

image


Unfortunately, this is not good enough for the Native Image. Once the native image is produced, the JAR file is irrelevant. The Native Image generator also does not somehow read all resources from the JAR and embeds these in the native image. What are my options? Well, I guess that I either can manually embed the contents of the validatorbundled.js file in a Java Class to ensure that the string content of the file is part of the Java code that makes it into the binary executable. This would work. And I can perhaps even make this into an automated step into my build pipeline.

Much more elegant is the approach that leverages ‘image build time class initialization”. Java Classes can contain a static initializer – code to be executed at Class load time instead of Object creation time (per Class instance). GraalVM Native Image can be instructed to initialize classes when creating the native image, to avoid running the same initialization code over and over again at every application start up. Objects created during image build time are available at run time – stored in the so-called image heap. Note that the GraalVM Feature API provides more fine grained control.

image

This translates in my current situation to the following: When creating a native image for a Java application that calls JavaScript that is loaded from an external file … we want this file loading to take place in a static initializer and instruct the GraalVM native image tool to initialize the class that loads this external resource at image build time. The JavaScript is loaded from the external file and its content stored in a String on the Image Heap inside the native image.

image

The command line instruction to create the native image includes the –initialize-at- build-time switch to instruct GraalVM to initialize the class that loads the JavaScript resource at image build time.

$GRAALVM_HOME/bin/native-image -cp ./application-bundle.jar <br>--language:js -H:Name=postalCodeValidator<br>-H:Class=nl.amis.java2js.ValidateThroughNPMValidator <br>--initialize-at-build-time=nl.amis.java2js.ValidateThroughNPMValidator <p>-H:+ReportUnsupportedElementsAtRuntime --allow-incomplete-classpath


The Java Class with the static initializer is coded as follows:

package nl.amis.java2js;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.graalvm.polyglot.*;

public class ValidateThroughNPMValidator {

	private Context c;
	// store JavaScript resource read from file (for example from JAR)
	private static String validatorbundledFileName ="/validatorbundled.js";
	private static String validatorbundledJsResource;
        // static Class initializer - to be executed at image build time
	static {
        System.out.println("Inside Static Initializer.");
		try {
			// load file validatorbundled.js from root of Java package structure aka root of
			// JAR archive file
			InputStream is = ValidateThroughNPMValidator.class.getResourceAsStream(validatorbundledFileName);
			
			ByteArrayOutputStream result = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int length;
			while ((length = is.read(buffer)) != -1) {
			    result.write(buffer, 0, length);
			}
			// StandardCharsets.UTF_8.name() > JDK 7
			validatorbundledJsResource= result.toString("UTF-8");			
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
        System.out.println("End Static Initializer.\n");
    }

	public ValidateThroughNPMValidator() {
		// create Polyglot Context for JavaScript and load NPM module validator (bundled
		// as self contained resource)
		c = Context.create("js");
		c.eval("js",validatorbundledJsResource);
		System.out.println("All JavaScript functions now available from Java (as loaded into Bindings) "
				+ c.getBindings("js").getMemberKeys());
	}


	public Boolean isPostalCode(String postalCodeToValidate, String country) {
		// use validation function isPostalCode(str, locale) from NPM Validator Module
		// to validate postal code
		Value postalCodeValidator = c.getBindings("js").getMember("isPostalCode");
		Boolean postalCodeValidationResult = postalCodeValidator.execute(postalCodeToValidate, country).asBoolean();
		return postalCodeValidationResult;
	}

	public static void main(String[] args) {
		for(int i = 0; i < args.length; i++) {
            System.out.println("Args "+i+": "+args[i]);
        }
		ValidateThroughNPMValidator v = new ValidateThroughNPMValidator();
		System.out.println("Postal Code Validation Result " + v.isPostalCode("3214 TT", "NL"));
		System.out.println("Postal Code Validation Result " + v.isPostalCode("XX 27165", "NL"));
	}

}


Keeping the external resources external – loading resources from the native image at runtime

A third option is that the Java Class can load the JavaScript resource in two different ways: as a resource from the JAR file (as previously) or from an absolute path on the local file system – to be specified through a program parameter passed when running the executable. With this option, the JavaScript resource stays an external resource that has to be around when the native image is started, complicating distribution, installation and actually running the executable.

image

The Java Class is coded as follows:

package nl.amis.java2js;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import org.graalvm.polyglot.*;

public class ValidateThroughNPMValidator2 {

	private Context c;

	public ValidateThroughNPMValidator2() {
		try {
			// load file validatorbundled.js from root of Java package structure aka root of
			// JAR archive file
			InputStream is = getClass().getResourceAsStream("/validatorbundled.js");
			readAndEvaluateJavaScriptSource(is);
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public ValidateThroughNPMValidator2(String validatorBundleSourceFile) {
		try {
			// load file validatorbundled.js from root of Java package structure aka root of
			// JAR archive file
			System.out.println("Loading Validator Module from " + validatorBundleSourceFile);
			InputStream is = new FileInputStream(validatorBundleSourceFile);
			readAndEvaluateJavaScriptSource(is);
			is.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void readAndEvaluateJavaScriptSource(InputStream is) throws IOException {
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader JSreader = new BufferedReader(isr);

		// create Polyglot Context for JavaScript and load NPM module validator (bundled
		// as self contained resource)
		c = Context.create("js");
		ClassLoader classloader = Thread.currentThread().getContextClassLoader();
		// load output from WebPack for Validator Module - a single bundled JS file
		c.eval(Source.newBuilder("js", JSreader, "validatorbundled").build());
		System.out.println("All functions available from Java (as loaded into Bindings) "
				+ c.getBindings("js").getMemberKeys());
		JSreader.close();
		isr.close();
	}
	
	public Boolean isPostalCode(String postalCodeToValidate, String country) {
		// use validation function isPostalCode(str, locale) from NPM Validator Module
		// to validate postal code
		Value postalCodeValidator = c.getBindings("js").getMember("isPostalCode");
		Boolean postalCodeValidationResult = postalCodeValidator.execute(postalCodeToValidate, country).asBoolean();
		return postalCodeValidationResult;
	}

	// pass 
	public static void main(String[] args) {
		for(int i = 0; i < args.length; i++) {
            System.out.println("Args "+i+": "+args[i]);
        }
		// if the filename was passed as startup argument, then load the validator bundle according to that specification
		ValidateThroughNPMValidator2 v = args.length>0?new ValidateThroughNPMValidator2(args[0]):new ValidateThroughNPMValidator2();
		System.out.println("Postal Code Validation Result " + v.isPostalCode("3214 TT", "NL"));
		System.out.println("Postal Code Validation Result " + v.isPostalCode("XX 27165", "NL"));
	}

}

The command passed to GraalVM native image generation is:

$GRAALVM_HOME/bin/native-image -cp ./application-bundle.jar --language:js -H:Name=postalCodeValidator -H:Class=nl.amis.java2js.ValidateThroughNPMValidator --verbose -H:+ReportUnsupportedElementsAtRuntime --allow-incomplete-classpath

The Java application to be turned native is packaged in a JAR file. The JavaScript runtime engine should be included for runtime polyglot purposes. The entry point into the Java application is the main method in class ValidateThroughNPMValidator . The native image to be produced is called postalCodeValidator.

The outcome – after a wait time of several minutes- is a binary executable that contains the JavaScript runtime execution engine – but not yet the Validator module. When the binary executable is invoked, it loads the JavaScript from the path specified and evaluates the code into JavaScript functions that subsequently are invoked by the native code created from the Java source shown overhead.

image

As an aside: on my non-optimized system, the startup time difference between the traditional JIT execution style and the native image is quite staggering: 0.08 vs 4.99 seconds:

image



Resources

GitHub Repository with sources for this article: https://github.com/AMIS-Services/jfall2019-graalvm

Medium article on class initialization at GraalVM Image Build Time: https://medium.com/graalvm/updates-on-class-initialization-in-graalvm-native-image-generation-c61faca461f7

My article in which I explain how to create the Java application that leverages the JavaScript NPM Validator Module: https://technology.amis.nl/2019/10/25/leverage-npm-javascript-module-from-java-application-using-graalvm/

GraalVM Docs on Native Image generation: https://www.graalvm.org/docs/reference-manual/native-image/

Intro to GraalVM on Ben’s Corner: https://fedidat.com/510-intro-to-graal/ 

Docs on GraalJS and Interoperability with Java – https://github.com/graalvm/graaljs/blob/master/docs/user/NodeJSVSJavaScriptContext.md

JavaDocs for GraalVM Polyglot – https://www.graalvm.org/truffle/javadoc/org/graalvm/polyglot/package-summary.html

GraalVM Docs – Polyglot – https://www.graalvm.org/docs/reference-manual/polyglot/