This problem is not unique to J2V8, and will likely affect all JNI developers. In this article we will describe how we solved this problem.
Consider the following C++ example.
In this example, we are instantiating a V8Runtime in C++. This V8Runtime is created on the heap and can be referenced through the runtime pointer. For example, you could create a context and reset the global object as follows:
But how do you access this pointer in subsequent calls? You cannot return the runtime object to Java since it’s not a Java Object?
The first solution we explored was to store all C++ objects in a map and reference them through a unique ID. The unique ID should be a Java primitive like an int / long or a String. Each time a C++ object is created, a pointer to that object is stored in the map. The map is indexed by a key, and that key is returned to Java.
The handle can be stored in Java as a primitive (Java long in this case), and whenever you need to reference the C++ object you can pass the handle through the JNI bridge. This works but has a number of limitations:
- How do you generate a UID? We used a basic counter, but this is not thread safe. Also, we used an int which wrapped pretty quickly.
- Lookup may be slow depending on how many objects you have in the system.
- The map is a shared resource and care must be taken to ensure multiple threads do not write to it simultaneously.
To solve this problem we moved to a much easier solution, we now just store the memory address of the C++ object. Since Java doesn’t have a pointer type, we casted the memory address to a jlong (Java long) since this is big enough to store addresses up to 64 bits. While reinterpret_cast is usually not a cast you want to perform, it’s exactly what you want in this case.
reinterpret_castis the most dangerous cast, and should be used very sparingly. It turns one type directly into another – such as casting the value from one pointer to another, or storing a pointer in an
int, or all sorts of other nasty things.
With reinterpret_cast, we can cast a pointer to a jlong which can be stored in any Java object. The jlong can then be casted back to the pointer. This provides constant time lookup and a thread safe way to manage objects.
Finally, remember that whatever solution you choose, you still need to free all memory you allocate in C++.
We recently moved all J2V8 memory storage to this approach. This is available in J2V8 3.1.0-SNAPSHOT. If you are using J2V8, please test this and report any bugs you see. I would like to release J2V8 3.1.0 by mid November.
For more J2V8 Tips & Tricks, follow me on Twitter.