11 July, 2009

Embedding Java in a C language application

The Java Native Interface is a powerful API that allows Java to call native libraries and also allows native executables to invoke Java (or any JVM-based) classes.

An important addition to the Java Native Interface in Java 1.4 was support for java.nio.ByteBuffer parameters. This allows the native code (C in my case) to efficiently share data with classes running in the JVM. Often, when C code is invoking Java methods, it has to create objects, particularly Strings, to pass to the methods. While this provides some safety to the Java side, it has some performance implications.

With Java 1.4, we have the option to pass data across the JNI in a single buffer shared by the C and Java code referenced by a java.nio.ByteBuffer. The use of this technique comes with many caveats, particularly if the application runs multiple threads. The ideal application for this approach is when a fixed-size message with an unvarying or self-defining format is passed across the interface. The performance advantage comes because both the C and Java code can change the data, so the ByteBuffer object only has to be created once. The C code can change the data in the buffer and pass the same ByteBuffer object to multiple method invocations and then make use of changes the Java code makes to the buffer contents.

JNIEnv *jni = get_jniEnv();
jclass cls = getClassRef();
jmethodID getdata_mid = getMethodID(DATA_LOADER);
static jobject jbuff = NULL;
static char *databuff = NULL;

if (!databuff)
  if (databuff = malloc(cbuff))
    memset(databuff, '\0', cbuff);

if (jni && !jbuff && databuff)
  jbuff = (*jni)->NewDirectByteBuffer(jni, databuff, cbuff);
if (!(jni && cls && getdata_mid && jbuff))
  //quit unless all references are available
  return NULL;
(*jni)->CallStaticVoidMethod(jni, cls, getdata_mid, jbuff);
if (check_exception())
  return NULL;  
return databuff;
The code above assumes that all the work to initialize the JVM, load the class and lookup the method is handled elsewhere. The caller can examine and change the data retrieved by the Java code. It can then modify the contents of the buffer returned and call the function again; then Java code can use the changes made in buffer between the calls. This saves object creation and destruction as well as the time copying data from one buffer to another.

On the Java side, there are some limitations on the use of the ByteBuffer. It's not backed by an Array object, so some of the operations declared by the abstract class are unsupported when the object is created by the C code.

JRE Internal Error – "exception happened outside interpreter, nmethods and vtable stubs (1)"?!

When calling Java via JNI, there's no checking of the method parameters. As a result, if your code passes parameters that don't match the method signature in a Call...Method JNI call, the JVM will likely panic and terminate with a message like "exception happened outside interpreter, nmethods and vtable stubs (1)" if you're lucky (and your JVM is at least 1.6). If you're really lucky, the JVM will write an error log that includes a stack trace pointing to the C code that made the JNI call.

Unfortunately, the 'Internal Error' reported by the JVM is really a generic panic message, so it's also likely to result from more insidious problems like your code writing into the Java heap. In that case you could try valgrind or similar tool to track down stray memory usage, but I can't say whether it'll work when calling into the JNI.

2 comments:

  1. If it would be helpful to provide a working example program using ByteBuffer, please let me know. If you suggest a simple use case, I might be able to provide an implementation.

    ReplyDelete
  2. This comment has been removed by a blog administrator.

    ReplyDelete

 

Copyright 2009-2010 John Bito. Creative Commons License
This work is licensed under a Creative Commons Attribution-NoDerivs 3.0 Unported License.