J2V8

Getting Started

J2V8 is a set of Java bindings for Google’s popular JavaScript engine, V8. It was developed to bring highly efficient JavaScript to Android and is the workhorse behind Tabris.js. J2V8 also runs on Windows, Linux and Mac OS. In this tutorial we will demonstrate how to execute scripts with J2V8.

Primitive When Possible

J2V8 was designed with performance and memory consumption in mind. If the result of a JavaScript execution is a 32bit integer, then it can be directly accessed as a primitive without first creating an instance of a wrapper class. The same is true for 64 bit doubles and booleans.

J2V8 also uses a lazy loading technique. That is, only when a JavaScript result is accessed does it copy it across the JNI Brdige. For example, if a large JavaScript array is returned, the contents of the array are not loaded until the individual elements are needed.

Native Handles

J2V8 is simply a set of Java bindings for V8 which exposes the V8 API in Java. V8 is written in C++. To access V8, J2V8 uses the Java Native Interface (JNI). Because of the native interaction, C++ memory must be managed. J2v8 helps with this, but requires the developer to explicitly release any native handles by calling release() when an Object is no longer needed. Releasing an object does not free it from JavaScript (the V8 Garbage Collector does this); release simply removes the native handle. The rules for releasing resources are simple:

  1. If you created it, you must release it, with one exception; if the object is being passed back via a return statement, the system will release it for you.
  2. If the system created it, you don’t need to worry about it, with one caveat; if the object was returned to you as a result of a method call, you must release it.

To help you manage the native handles, J2V8 can be told to report any memory leaks during shutdown.

Threading Model

JavaScript itself is single threaded and J2V8 enforces this. All access to a single runtime must be from the same thread. This ensures that there are no race conditions (or deadlock potential) when manipulating or working with a single JavaScript runtime.

While J2V8 ensures that all access to a single runtime must come from the same thread, you can create multiple runtimes, each on their own thread. With this model you can easily implement WebWorkers.

Getting J2V8

J2V8 is available in Maven Central. The most recent version is 2.2.1. The following can be used in your pom.xml to depend on J2V8.

com.eclipsesource.j2v8
  j2v8_win32_x86_64
  2.2.1

This will fetch J2V8 for Windows 64bit. Other platform specific runtimes include:

  • j2v8_win32_x86
  • j2v8_android_x86
  • j2v8_android_armv7l
  • j2v8_macosx_x86_64

Hello, World!

To understand J2V8 in practice, let’s create a Hello, World script that concatenates two strings and returns the length of the result.

var hello = 'hello, ';
 var world = 'world!';
 hello.concat(world).length;

With J2v8 you must first create a runtime. A static helper factory method will do this for you. Creating a runtime will also load the native library.

public static void main(String[] args) {
 V8 runtime = V8.createV8Runtime();
 int result = runtime.executeIntegerScript(""
  + "var hello = 'hello, ';\n"
  + "var world = 'world!';\n"
  + "hello.concat(world).length;\n");
 System.out.println(result);
 runtime.release();
}

Once the runtime is created, you can execute scripts on it. There are several script execution methods depending on the return type. In this example we used executeIntegerScript because the result was an int and there is no casting or wrapping required. When the application terminates, the runtime must be released.

Accessing JavaScript Objects

With J2V8 you can get deep handles into JavaScript objects from Java. Consider the following JavaScript:

public static void main(String[] args) {
  V8 runtime = V8.createV8Runtime();
  runtime.executeVoidScript(""
    + "var person = {};\n"
    + "var hockeyTeam = {name : 'WolfPack'};\n"
    + "person.first = 'Ian';\n"
    + "person['last'] = 'Bull';\n"
    + "person.hockeyTeam = hockeyTeam;\n");
  // TODO: Access the person object
  runtime.release();
}

After the script has been executed, you can access any global variables simply by their name. In this case we could access the person object and start walking the object graph from there.

V8Object person = runtime.getObject("person");
  V8Object hockeyTeam = person.getObject("hockeyTeam");
  System.out.println(hockeyTeam.getString("name"));
  person.release();
  hockeyTeam.release();

Since the V8Object is just a handle to the underlying JavaScript object, V8Objects can be manipulated. For example, we could add another field hockeyTeam.add("captain", person); and it would be immediately accessible from JavaScript.

assertTrue(runtime.executeBooleanScript("person === hockeyTeam.captain"));

V8Object also provides a few other helpful methods. getKeys() will return the keys associated with the Object. getType(String key) will return the type of object associated with a key. With these two methods, you can dynamically traverse a complex object graph.

Finally, the V8Objects we have accessed must be released when we no longer need them. They will still exist in JavaScript if they are accessible from the root. The reason they need to be released is because they were returned to us as a result of a method call (See point #2 above).

V8Arrays

Just as V8Objects can be accessed from Java, V8Arrays can also be passed across the bridge. V8Arrays extend V8Objects, and provide all the same accessor / mutator methods. In addition to these, the elements of a V8Array can be access by index. Both V8Arrays and V8Objects follow a fluid programming model, which makes it very easy to construct new JavaScript objects.

V8Object player1 = new V8Object(runtime).add("name", "John");
V8Object player2 = new V8Object(runtime).add("name", "Chris");
V8Array players = new V8Array(runtime).push(player1).push(player2);
hockeyTeam.add("players", players);
player1.release();
player2.release();
players.release();

Of course, V8Arrays must also be released when they are no longer needed.

Calling JavaScript Functions

In addition to executing scripts, Java can call JavaScript functions using J2V8. Functions can be either global or attached to another Object and can optionally return a result. Consider the following JavaScript function:

var hockeyTeam = {
     name      : 'WolfPack',
     players   : [],
     addPlayer : function(player) {
                   this.players.push(player);
                   return this.players.size();
     }
}

To call this method from Java, we only need a handle to the hockeyTeam object. With the handle, we can invoke functions in the same way we invoke scripts. However, unlike scripts, functions can also be passed a V8Array of parameters.

V8Array parameters = new V8Array(runtime).push(player1);
int size = hockeyTeam.executeIntegerFunction("addPlayer", parameters);
parameters.release();

The elements of the parameter array are mapped to the parameters of the JavaScript function. The number of parameters in the Array and the number of parameters specified on the function don’t need to match. undefined will be used as a default. Finally, the parameters array must be released.

Summary

J2V8 is a set of Java bindings for the popular V8 JavaScript engine. J2V8 brings highly efficient JavaScript to Android and other Java based systems. In this tutorial we saw how to interact with a V8 runtime using J2V8. In particular, we saw how to interact with V8Objects, V8Arrays, execute scripts and call JavaScript functions.

In the next issue we will look at registering Java callbacks.

For more information about J2V8, follow me on Twitter.