Efficient JNI programming III: Array access

The last two parts of this small series dealt with considerations when to implement stuff in a native JNI method and how to best implement field and method access in JNI. Now I will take a close look at another common cause of headaches, array access in JNI.

Let me start with a small example of how array access is usually implemented in JNI:

JNIEXPORT void JNICALL Java_JNIStuff_myNativeMethod(JNIEnv* env, jintArray myArray) {
jint* buf;
(*env)->GetIntArrayElements(env, myArray, NULL);
// Do something with buf.
}

Ok, what does this function do and what could be wrong with it? Basically, it takes a Java int array and creates a C jint array of it. If you are really lucky, and the datastructure for Java int arrays in the VM is the same as a C jint array, and the Java VM supports pinning of arrays, this is just fine. However, if not all of the above is true, then the VM will copy the Java array contents into an equivalent jint*. And we wouldn’t know this, because we supplied NULL as last argument. We could have supplied a jboolean*, into which the VM could have put a notice if the VM has made a copy of the array. So let me explain what could lead to array copying:

  • The VM can’t ‘pin’ the array. Many VMs can move around Java arrays, for example when compacting the heap. When a JNI method grabs such an array, the array needs to be pinned, so that the garbage collector can’t move it around in the meantime. This usually hinders or even halts the GC in its work, so this is not a desirable state for a longer time. In an effort to prevent GC blocking, a VM can decide to copy the array.
  • The VM doesn’t store Java arrays as contiguous C arrays. An example are realtime VMs that need to do this in order to guarantee realtime behaviour.

So, depending on the VM and the actual implementation of the rest of the method,the above code snipped might be perfectly OK to go with or it can prove to be a serious performance bottleneck. Imagine you pass a large array to the native method and only read a couple of bytes out of it, and need to copy the whole array for this.

So, let me discuss the different way to handle arrays in JNI.

Get<XYZ>ArrayElements()

This is what is used in the above example. This may or may not copy the Java array, but if in doubt, it more likely will make a copy. Use this when you do longer processing of a Java array on the native side. Don’t forget to Release<XYZ>ArrayElements to release the array.

Get<XYZ>ArrayRegion()

This always makes a copy, but only of a region of the Java array. When you have a large Java array and only need a small portion of it, then use this. If you make modifications, you can use Set<XYZ>ArrayRegion().

GetPrimitiveArrayCritical()

This has been introduced in JDK1.3 and is sematically identical to Get<XYZ>ArrayElements, but the VM would try really hard to not make a copy. It would give you the real thing even if it would block the GC. For this reason, use this function only when you can release the array quickly. As with Get<XYZ>ArrayElements, don’t ever forget to ReleasePrimitiveArrayCritical().

Direct ByteBuffer

As of JDK1.4, you can use NIO direct ByteBuffer to transfer bulk data to native code. Direct byte buffers reference a native array directly, thus accessing this on the JNI side doesn’t cause much headache. Use GetDirectBufferAddress() to get a pointer to the memory region, and GetDirectBufferCapacity() to get the length of the buffer in bytes. You can access this region like you would access any other C array without the need to release the array and without the need to think about possible copies. The tradeoff is that ByteBuffers are usually slower to access from the Java side, because this involves a bunch of method calls and bookkeeping. Use direct ByteBuffer when you access the data mainly on the JNI side, and little to never on the Java side. You should also consider that direct byte buffers live outside the Java VM, so it is a little hard to measure (e.g. for profiling tools) how much memory a direct ByteBuffer actually uses.

Advertisements

3 Responses to Efficient JNI programming III: Array access

  1. Alan says:

    The HotSpot VM has intrinsics to make access to byte buffers very fast from java code.

  2. Lorenzo Bigagli says:

    Thanks for the useful clarification.

    I have a problem that may be related to the above issue: in a frequently called C method, I create an int array with NewIntArray, fill it appropriately with SetIntArrayRegion and pass it on to a Java method with CallVoidMethod.

    The newly created array is not used anymore (nor it is accessible) from C nor from Java, after these calls.
    After a while, I get an heap overflow.

    Should I release it before leaving the C method? Or?
    Many thanks again.

  3. Roman Kennke says:

    Lorenzo: Yes, releasing arrays that have been created in JNI is extremely important. I suggest to always do New***Array and Release***Array symmetrical, i.e. in the same JNI function.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: