TypedArrays -- Sharing Memory Between V8 and Java

July 25, 2016 | 3 min Read

J2V8 provides the technology to link V8 and Node.js with the JVM. Using JNI, Java developers can directly call V8 and Node.js APIs. This seamless integration brings improved JavaScript performance, NPM modules and the Node.js APIs to the Java ecosystem. An alternative approach could have been to invoke two separate processes (Java and Node.js) with a service protocol between them. However, one of the advantages of the single process approach is the ability to share memory between JavaScript and Java. In this article we will explore how we use TypedArrays to provide a shared memory space between V8 and the JVM.

Typed arrays are array-like objects and provide a mechanism for accessing raw binary data. The TypedArrays specification describes both the backing store (the ArrayBuffer) and the views onto that store (Int32Array, for example). The ArrayBuffer is designed for in-memory assembly of large blocks of data which can be mapped directly onto continuous memory regions such as the memory on a GPU. In J2V8 4, ArrayBuffers are mapped directly onto a Java ByteBuffer (java.nio.ByteBuffer). This means that as bytes are written to TypedArrays in JavaScript, they are immediately available in Java and vice versa. J2V8 ensures that the ByteOrder of the array buffer matches your system architecture.

Handle arrayBuffer = Local::New(isolate, *reinterpret_cast<Persistent*>(objectHandle));
void* dataPtr = arrayBuffer->GetContents().Data();
jobject byteBuffer = env->NewDirectByteBuffer(dataPtr, capacity);
return byteBuffer;

V8ArrayBuffer contain a method called getBackingStore() which provides direct access to the java.nio.ByteBuffer.

This is very useful, for example, if you have a typed array containing large binary data such as an image. This typed array can be processed in Java or JavaScript without copying the data across the JNI bridge. This produces a very powerful programming model where you can move between JavaScript (Node.js) and Java.

In this example we use Jimp to load an image in JavaScript.

var Jimp = require('/Users/irbull/node_modules/jimp');
Jimp.read('/Users/irbull/Downloads/IMG_20160706_175824.jpg', (err, image) => {
  if (err) throw err;
  process_in_java(image.bitmap.data);
  image.write('/Users/irbull/Downloads/output.jpg');
});

Instead of processing the image in JavaScript however, we will process each byte in Java. In this simple example we will create a grayscale image using the Simple Averaging Algorithm.

V8TypedArray typedArray = (V8TypedArray) parameters.get(0);
ByteBuffer buffer = typedArray.getByteBuffer();
try {
  for(int i = 0; i < buffer.limit(); i+=4) {
    int red = 0xFF & buffer.get(i);
    int green = 0xFF & buffer.get(i+1);
    int blue = 0xFF & buffer.get(i+2);
    int gray = (int) (red + green + blue ) / 3;
    buffer.put(i, (byte) gray); 
    buffer.put(i+1, (byte) gray);
    buffer.put(i+2, (byte) gray);
  };
} finally {
  typedArray.release();     
}
return null;
}

Since both Java and JavaScript are operating on the same byte array, no expensive data copying is performed. Furthermore, we simply call image.write to save the image. This could be called in either Java or JavaScript. The complete code for this example is available on GitHub.

TypedArrays can also be created in Java and passed directly to JavaScript.

V8ArrayBuffer buffer = new V8ArrayBuffer(v8, 100);
new V8TypedArray(v8, buffer, V8Value.INT_16_ARRAY, 0, 50);

V8TypedArrays take the ArrayBuffer, view type, offset and length. The view type specifies how the bytes should be interpreted. In this case, all values should be interpreted as signed, 16bit integers. This is similar to how TypedArrays are specified in JavaScript.

For more tips, tricks, examples and J2V8 news, follow me on Twitter.

Ian Bull

Ian Bull

Ian is an Eclipse committer and EclipseSource Distinguished Engineer with a passion for developer productivity.

He leads the J2V8 project and has served on several …