JNI: Tracking Native References

JNI: Tracking Native References

When working with JNI, sooner or later you will need to track references to native objects. This came up in J2V8 pretty quickly. With J2V8 we need to keep a handle to the V8 Isolate (an isolated V8 runtime for executing Javascript on). All JNI calls to V8 need to first acquire the isolate, and then perform operations such as executeScript, getValue, setValue, callFunction, etc… The challenge, acquiring the isolate.

In J2V8, when you first create a runtime, a number of things happen under the hood.

  V8Runtime* runtime = new V8Runtime();
  runtime->isolate = Isolate::New();
  runtime->isolate_scope = new Isolate::Scope(runtime->isolate);
  HandleScope handle_scope(runtime->isolate);

As you can see, we create an Isolate, IsolateScope and HandleScope. These are stored as fields on a V8Runtime object so they can be accessed later, for example, when a script is executed or a JS variable is accessed.

  Handle object = Local::New(isolate, jsObject);
  return object->Get(key);

But how do you access the isolate? Because we may have several isolates (one for each tab in a web browser, for example), we cannot store it as a global variable. One option is to assign each isolate a unique ID and store it in a map. This is what I first did in J2v8, but as soon as you add multiple threads then this map becomes a shared data structure and you need to have a read / write lock around it. A better way is just to store the native reference with the Java object.

Since you cannot actually assign native C++ objects to fields of a Java Object, you just store the pointer. You can use an int, but to ensure things also work on 64 bit architecture I suggest storing the pointer as a long. In C++, you can cast the object reference to a long using reinterpret_cast.

long ptr = reinterpret_cast(runtime);

This value can now be passed directly to Java and stored as a field within your object. Whenever you need to deference the pointer, you cast it back.

  V8Runtime* runtime = reinterpret_cast(ptr);

I’ve converted the runtime map in J2V8 to use this technique. Without the read/write lock, the performance for the multi-thread support improved 4x. More details to follow.

For more information on J2V8, follow me on twitter.