|
java performancePerformance Issues with the Java Native Interface
by Glen McCluskey
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
public class sum {
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
$ 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
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
} 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
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
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
To fix this problem, we can rewrite the C++ code as: #include <jni.h> extern "C" {
JNIEXPORT jint JNICALL
} 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++
Summary
|
|
Last changed: 17 Apr. 2000 mc |
|