Implementing only the innermost method in a JNI call provides the most crossings of the JNI
boundary ( numberOfTrials * numberOfLoops , or 10 million). Reducing the number of
crossings to numberOfTrials (10,000) reduces that overhead substantially, and reducing it
further to 0 provides the best performance—at least in terms of JNI, though the pure Java im-
plementation is just as fast as an implementation that uses all native code.
JNI code performs worse if the parameters involved are not simple primitives. Two aspects
are involved in this overhead. First, for simple references, an address translation is needed.
This is one reason why, in the example above, the call from Java to C experienced more
overhead than the call from C to Java: calls from Java to C implicitly pass the object in ques-
tion (the this object) to the C function. The call from C to Java doesn't pass any objects.
Second, operations on array-based data are subject to special handling in native code. This
includes String objects, since the string data is essentially a character array. To access the
individual elements of these arrays, a special call must be made to pin the object in memory
(and for String objects, to convert from Java's UTF-16 encoding into UTF-8). When the ar-
ray is no longer needed, it must be explicitly released in the JNI code. While the array is
pinned, the garbage collector cannot run—so one of the most expensive mistakes in JNI code
is to pin a string or array in code that is long-running. That prevents the garbage collector
from running, effectively blocking all the application threads until the JNI code completes. It
is extremely important to make the critical section where the array is pinned as short as pos-
Sometimes, this latter goal conflicts with the goal of reducing the calls that cross the JNI
boundary. In that case, the latter goal is the more important one: even if it means making
multiple crossings of the JNI boundary, make the sections that pin arrays and strings as short
1. JNI is not a solution to performance problems. Java code will almost always run
faster than calling into native code.
2. When JNI is used, limit the number of calls from Java to C; crossing the JNI
boundary is expensive.
3. JNI code that uses arrays or strings must pin those objects; limit the length of time
they are pinned so that the garbage collector is not impacted.