Java Native Interfaces of C++ Execution Engine.


Overview

VoltDB mainly consists of Java modules for easier development. However, the core EE and its underlying on-memory storage system is implemented in C++. Nowadays Java and C++ have almost same performance for many cases, but C++ still outperforms Java on low-level memory accesses. This is why we have EE and its storage system written in C++. The Java native interface works as a bridge between Java modules and C++ EE.


Files and Compilation

The JNI entry points are defined in:

build/trunk/MIT_ACR/EE/src/hstorejni.cpp

which includes the JNI skeleton header file:

build/trunk/MIT_ACR/EE/src/org_voltdb_jni_ExecutionEngine.h

output by JDK's javah command on:

build/trunk/GPL_VCR/gpl_java/gpl_src/org/voltdb/jni/ExecutionEngine.java

The org.voltdb.jni.ExecutionEngine Java class also defines several public static final constant values exported to C++ side by javah command. The compilation does following:

  1. Compile Java classes

  2. Execute javah on the class files to refresh JNI skeleton header

  3. Compile C++ files

  4. Link object files to a native shared library

The name of JNI shared library is "voltdb" (VoltDB Execution Engine) and the actual library file compiled by GCC is named "libvoltdb.so" in Linux, "libvoltdb.jnilib" in MacOSX and so on, depending on the default prefix and extension of shared library on the platform. See MIT_ACR/EE/platform/platform-XXX-defines.mk and MIT_ACR/EE/src/makefile for more details about compile/link options.

In anyway, the library is loaded to JVM in ExecutionEngine Java class by the name "voltdb".


Basic Rules

An ExecutionEngine Java object corresponds to an HStoreEngine C++ object defined in MIT_ACR/EE/src/execution/hstoreengine.h, cpp. Each method in ExecutionEngine Java class has corresponding method in HStoreEngine C++ class and its JNI interface in hstorejni.cpp.

To make JNI method calls simple and fast, all JNI methods should satisfy 3 rules.

1. First parameter should be the EE pointer.

hstorejni.cpp is designed to be stateless. It uses no local or global variables in it. Instead, nativeCreate() method creates HStoreEngine C++ object and returns the pointer to Java as a jlong value. ExecutionEngine Java object holds the pointer value until the Java object is released and passes it to JNI methods each time. By this way, multiple C++ EE instances in same shared library space can simultaneously work without any conflict between them.

2. Return value should be an error code.

For portability, the JNI methods don't throw exceptions. Instead, they return jint value as an error code defined in ExecutionEngine. ExecutionEngine converts the error code to RuntimeException and appropriate message and throws it in Java side.

3. Parameters and return values should be in primitive types

They should be simple primitive values or arrays of primitive types. Java.lang.String is also used, but only in the case where the characters are guaranteed to be in US-ASCII charset.

The only exception of above rule is nativeSerializeTable(), which returns the whole content of a table stored in C++ EE.

Also, for type-safety, the public Java method receives objects, not primitive values. See the class comment of ExecutionEngine for more details.