;login: The Magazine of USENIX & SAGEProgramming

 

java performance

Performance Issues with the Java Native Interface

mccluskey_glen

by Glen McCluskey
<glenm@glenmccl.com>

Glen McCluskey is a consultant with 15 years of experience and has focused on program-ming languages since 1988. He specializes in Java and C++ per-formance, testing, and technical documentation areas.

 

The Java Native Interface (JNI) is a mechanism in Java that allows a Java program to call functions in other languages such as C++. A variety of issues come up with JNI use, including some performance ones, and it's instructive to step through an example and look at some of these.

Summing the Values in an Array
Suppose that we have a Java program that calls a C++ function to sum the values in an array, with -1 used as an array terminator value. The Java program looks like this:

   public class sum {
    static
    {
     System.loadLibrary("clib");
    }
    public static native int sum(int arr[]);
    public static void main(String args[])
    {
     int x[] = new int[5];
     x[0] = 37;
     x[1] = 47;
     x[2] = 57;
     x[3] = 67;
     x[4] = -1;
     int y = sum(x);
     System.out.println(y);
    }
   }

A method called from Java but defined in some other language has a "native" modifier in the declaration, and the method has no body (because the body will be supplied by the other language implementation).

Native methods are dynamically linked, and their implementations are found in a shared library or DLL. System.loadLibrary() is called to load the shared library. This loading is done when the Java program starts up (enclosing Java code in "static {...}" has this effect). The use of dynamic linking implies some performance overhead.

Once the Java program is defined, it is compiled by saying:

   $ javac sum.java

If the program is run at this point, an UnsatisfiedLinkError results, because the shared library that defines sum() has not yet been created.

Building the Shared Library
The first step in building the library is to generate a header file that declares the C++ sum() function. One way of generating the file is:

   $ javah -jni sum

using Java Development Kit 1.2 commands. The result is a file that looks like this:

   #include <jni.h>

   JNIEXPORT jint JNICALL
   Java_sum_sum(JNIEnv*, jclass, jintArray);

This is the declaration of the C++ function we need to implement to sum the elements of the array.

An actual implementation of the sum() function is:

   // clib.c

   #include <jni.h>

   extern "C" {

   JNIEXPORT jint JNICALL
   Java_sum_sum(JNIEnv* env, jclass, jintArray arr)
   {
    jint sum = 0;
    jint* p = env->GetIntArrayElements(arr, 0);
    for (jsize i = 0; p[i] != -1; i++)
     sum += p[i];
    env->ReleaseIntArrayElements(arr, p, 0);
    return sum;
    }

}

The extern "C" is a C++ notation that says that the enclosed function should have C name linkage rather than use C++-style external names. Once we've defined this function, we compile it and create a shared library containing it. For example, using C++Builder 4, we would say:

   $ bcc32 -c -Ip:/javanew/include -Ip:/javanew/include/win32 clib.c
   $ bcc32 -tWD clib.obj

to create clib.dll. We can then run the Java program:

   $ java sum

and it will print out a value of 208.

Details of How Sum() Works
If we go back to the C++ implementation of sum(), there are several points of interest. One basic issue is how Java and C/C++ differ in the way arrays are treated. In C/C++ an array is simply a contiguous region of storage. In Java an array is more complicated. It fits within the class-object hierarchy, so you can assign an array reference to an object reference. Java arrays have their length stored with them, which can be retrieved at any time by saying:

   int len = arr.length;

Array subscripts are checked at runtime for validity, with an exception thrown if they're out of range. Java has no pointers, and Java array values can be referenced only through subscripts or by using the reflection mechanism, so the Java runtime system is allowed flexibility in the way it stores arrays. For example, it's possible that the Java garbage collector might move an array to a different memory location. This is fine if you're using only Java, but it doesn't work at all if you've obtained a C-style pointer to a Java array and then it moves on you.

To solve this problem, a function such as GetIntArrayElements() may return an actual pointer to the Java array, if the Java garbage collector can guarantee that the array will not move, or else it will copy the array into a temporary location and return a pointer to the copy.

A final point about sum() is that a certain level of abstraction is implied by the JNI interface. For example, a Java array structure includes the actual elements, along with the length of the array. JNI does not provide access to the raw runtime array descriptor, but, rather, provides functions to obtain information about arrays. This abstraction is safer and more portable than using a lower-level interface.

Exception Handling
We said earlier that Java guarantees that array subscripts will be checked at runtime, but the implementation of sum() above does not honor this guarantee. What happens if the user fails to terminate the array value sequence with -1?

To fix this problem, we can rewrite the C++ code as:

   #include <jni.h>

   extern "C" {

   JNIEXPORT jint JNICALL
   Java_sum_sum(JNIEnv* env, jclass, jintArray arr)
   {
    jint sum = 0;
    jint* p = env->GetIntArrayElements(arr, 0);
    jsize maxlen = env->GetArrayLength(arr);
    jsize i = 0;
    while (i < maxlen && p[i] != -1)
     sum += p[i++];
    if (i == maxlen) {
     jclass exc =
      env->FindClass(
      "java/lang/ArrayIndexOutOfBoundsException");
     if (exc != NULL)
      env->ThrowNew(exc, "thrown from C++");
     }
    env->ReleaseIntArrayElements(arr, p, 0);
    return sum;
    }

   }

The subscript is checked before each array access. If the subscript overflows before -1 is found, an exception is thrown and propagated back to the Java program. If the Java program is run and sum() is called with an invalid array, the result is:

   java.lang.ArrayIndexOutOfBoundsException: thrown from C++
    at sum.sum(Native Method)
    at sum.main(sum.java:14)

Summary
The Java Native Interface is quite useful in accessing bodies of code written in other languages. We've illustrated some of the performance issues that come up with use of JNI. Additional material on JNI, including performance considerations, can be found in the book The Java Native Interface by Sheng Liang (Addison-Wesley, 1999).


 

?Need help? Use our Contacts page.
Last changed: 17 Apr. 2000 mc
Issue index
;login: index
USENIX home