Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
T E C H
T I P S
TIPS, TECHNIQUES, AND SAMPLE CODE
but for now they just use System.out.println to report that they
are incomplete. Go back and uncomment the calls to nativeMax and
nativeMaxCritical in Max.main, and try running the program.
Depending on which Java(tm) Runtime Environment (JRE) and
underlying OS you are using, one of several things might happen:
- the program might crash
- the program might run normally
- the program might fail with a "FATAL ERROR in native method"
This kind of unpredictable behavior never happens in Java
programs, but is standard for C++ programs. Unfortunately, JNI
code is similar to C++ code in that the behavior of the code
that mismanages memory is undefined. Undefined behavior is much
worse than a simple crash because you might not realize there
is a program bug. This is particularly true if the code often
runs normally (sometimes known as the "it worked on my machine"
syndrome). Undefined behavior makes finding code defects very
difficult.
JRE 1.2 and the classic VM of JRE 1.3 have a non-standard
command line option that can help you track down JNI bugs. Try
running the program again with the "-Xcheck:jni" option. If you
are running JRE 1.3, you will have to select the classic VM
with the classic option:
(if 1.2) java -cp . -Xcheck:jni Max
(if 1.3) java -classic -cp . -Xcheck:jni Max
If you are lucky, you will get the following descriptive error:
FATAL ERROR in native method: Bad global or local ref passed to JNI
at Max.nativeMax(Native Method)
at Max.main(Max.java:75)
It is a good idea to use the "-Xcheck:jni" flag during
development, but you should not count on this to find all
JNI-related problems. The best approach is careful analysis
of your java object references, plus code review.
In the example above, fixing the objOut reference is a simple
matter. Instead of a local reference, objOut should be stored in
a global reference. While a local reference is bound to a thread
and method call, a global reference lives until you specifically
delete it. The NewGlobalRef function creates a global reference
to any existing reference. Modify the JNI_OnLoad function,
that is, replace the following lines in JNI_Onload:
objOut = env->GetStaticObjectField(clsSystem, fidOut);
if (!objOut) return JNI_ERR;
with the following lines:
jobject localObjOut = env->GetStaticObjectField(clsSystem, fidOut);
if (!localObjOut) return JNI_ERR;
objOut = env->NewGlobalRef(localObjOut);
Notice that the static type of a global reference is the same as
the static type of a local reference (both are jobject). This
means that you must remember which references are global and which
are local; the compiler will not assist you. In the code above,
objOut holds a global reference which will prevent the garbage
collector from invalidating the reference. In this example,
a global reference provides exactly the desired behavior, keeping
the reference cached for the lifetime of the application. If you
need a reference to live longer than a method, but not forever,
you can match the call to NewGlobalRef() with a subsequent call to
DeleteGlobalRef().
If you recompile the C++ library with this new code, Max should
run correctly, and -Xcheck:jni should not report any problems.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ACCESSING ARRAYS IN JNI
Now that the object references are in order, it is time to
actually implement the max method in C++ code. If you scanned
jni.h, you would find three methods that offer access to an array
of Java integers:
GetIntArrayRegion(jintArray, jsize start, jsize len, jint *buf)
jint* GetIntArrayElements(jintArray array, jboolean *isCopy)
void* GetPrimitiveArrayCritical(jintArray array, jboolean *isCopy)
While each of these methods can be used to access any int array,
they have radically different semantics and performance
characteristics. Choosing the right one is critical to writing
correct, high-performance code.
GetIntArrayRegion is the simplest to use, because you never touch
the actual array data. Instead, you allocate a buffer, and some
portion of the array is copied into your buffer. Because the array
is copied, GetIntArrayRegion is rarely the best option for high
performance.
GetIntArrayElements asks the JRE to give you a pointer into
the actual array data. Sharing array memory with the JRE is
is called "pinning" the array, and when you are done you
must unpin the array with a call to ReleaseIntArrayElements.
Think of GetIntArrayElements as a polite request for a pointer
to the array data; it is not a demand for a pointer. You can use
the isCopy parameter to find out if your data is the actual array
data or your own private copy.
GetPrimitiveArrayCritical was added to JDK 1.2 to improve the
performance of array operations. Like GetIntArrayElements, the
critical API also asks the JRE for a pointer to the real data,
but this time the question is more of a demand. The critical API
tells the JRE to do everything possible to provide direct access.
This can include blocking other threads and even disabling all
garbage collection to guarantee safe access to the array data.
Because the JRE might be blocking many other operations while
you are accessing the array, you should exit the critical region
as soon as possible. Do this by calling
ReleasePrimitiveArrayCritical. Also, be careful not to call
other JNI functions, or do anything that could cause the current
thread to block.
Which array API is best for the max example? In the example, the
array data is traversed a single time and in read-only fashion.
They differ in the names of the Get/Release pair. The array code
itself is trivial. In fact, the only interesting detail is the
third parameter to the release function: JNI_ABORT. The JNI_ABORT
flag specifies that if you are using a local copy of the array,
there is no need to copy back to the real array. If you wind up
working with a copy of the array, this is a major performance
savings. Since the array was never written to, it's silly to copy
it back.
The behavior of GetIntArrayElements and GetPrimitiveArrayCritical
is not guaranteed. Either API can at any time return a copy or
a direct pointer to the data. This means that you have to test
your code on your specific JRE to determine whether you are
getting a performance boost from direct access.
Here is a summary of results obtained from testing the max
example on the 1.2 and 1.3 JREs. A debugger was used to check
the isCopy value. Benchmark code was used to compare the
performance of the three max implementations. You can find the
benchmark code at
http://staff.develop.com/halloway/JavaTools.html.
--------------------------------------------------------------Test
Copied Array?
Time (microsec)
--------------------------------------------------------------1.2 max
no
18
1.2 nativeMax
no
18
1.2 nativeMaxCritical
no
15
1.3 max
no
25
1.3 nativeMax
yes
27
1.3 nativeMaxCritical
no
15
--------------------------------------------------------------Key:
1.2 tests are with classic VM, JIT
1.3 tests are with the Java HotSpot(tm) Server VM
--------------------------------------------------------------It would be unwise to jump to any conclusions from these results.
The result will differ on different machines or with different
sized arrays. However the results do suggest that:
(1) Copying arrays is expensive. In the one case (1.3 nativeMax)
where the array was copied, performance was noticeably slower.
(2) Native code is not always faster then equivalent Java code.
Even when native code is faster, it doesn't represent an
order of magnitude improvement.
(3) It is difficult to benchmark HotSpot code. HotSpot tends to
fare poorly on benchmarks, but to shine in real applications
Also, a simple looping benchmark cannot tell you much about the
behavior of a heavily threaded (read: server) application. If a
JRE blocks other threads in order to give direct access to memory,
overall throughput can actually be worse with direct access to
arrays. In that situation, it would be better to use the
GetIntArrayRegion API to create a working copy of the array.
As you can see, JNI code becomes tricky to write as soon as you
begin to do any serious work. You must explicitly manage the
lifetime of objects by correctly choosing local or global
references, and run tests to determine the array accessor that