Received: from PACIFIC-CARRIER-ANNEX.MIT.EDU by po10 (5.61/4.7) id AA01995; Tue, 1 Aug 00 17:12:31 EDT
Received: from hermes.javasoft.com by MIT.EDU with SMTP
	id AA10454; Tue, 1 Aug 00 17:07:03 EDT
Received: (from nobody@localhost)
	by hermes.java.sun.com (8.9.3+Sun/8.9.1) id VAA24965;
	Tue, 1 Aug 2000 21:08:36 GMT
Date: Tue, 1 Aug 2000 21:08:36 GMT
Message-Id: <200008012108.VAA24965@hermes.java.sun.com>
X-Authentication-Warning: hermes.java.sun.com: Processed from queue /bulkmail/data/ed_6/mqueue5
X-Mailing: 234
From: JDCTechTips@sun.com
Subject: JDC Tech Tips  August 1, 2000
To: JDCMember@sun.com
Reply-To: JDCTechTips@sun.com
Errors-To: bounced_mail@hermes.java.sun.com
Precedence: junk
Mime-Version: 1.0
Content-Type: text/plain; charset=us-ascii
X-Mailer: Beyond Email 2.2


 J  D  C    T  E  C  H    T  I  P  S

                      TIPS, TECHNIQUES, AND SAMPLE CODE


WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, 
August 1, 2000. This issue is about the Java(tm) Native Interface 
(JNI). JNI is a powerful tool for building Java applications that
interoperate with other languages, especially C++. An important
thing to understand when you use JNI to integrate C++ code into 
a program written in the Java(tm) programming language is how JNI 
forces the Java and C++ memory management models to coexist in one 
process. This issue of the JDC Tech Tips covers two memory 
management issues that arise in JNI programming: 

         * Caching objects in JNI 
         * Accessing arrays in JNI  

These tips assume that you have some familiarity with JNI and that 
you know how to compile native JNI libraries with your C++ compiler 
of choice. If you are unfamiliar with JNI, see the Java Native 
Interface trail in the Java tutorial at
http://java.sun.com/docs/books/tutorial/native1.1/index.html
                
These tips were developed using Java(tm) 2 SDK, Standard Edition, 
v 1.3.

This issue of the JDC Tech Tips is written by Stuart Halloway,
a Java specialist at DevelopMentor (http://www.develop.com/java).

You can view this issue of the Tech Tips on the Web at
http://developer.java.sun.com/developer/TechTips/2000/tt0801.html

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
CACHING OBJECTS IN JNI

One of the features of JNI is that it allows your native code,
such as C++, to use Java objects. However this sometimes presents 
a problem in dealing with the "lifetime" of objects, that is, the
time between an object's allocation and deallocation. Java manages 
an object's allocation through new, and indirectly manages its
deallocation through garbage collection. However C++ requires 
explicit control of the entire lifetime through new and
delete. Because JNI straddles both the world of the Java language 
and C++, an awkward compromise must be reached. JNI provides 
explicit mechanisms to manage an object's lifetime, as in C++. But 
these mechanisms do not directly control lifetime. Instead, they 
give hints to the Java garbage collector. This creates a difficult 
situation for the developer. JNI object references have 
non-deterministic destruction, as in the Java environment; you
cannot determine specifically when an object's resources will be 
reclaimed. And misusing JNI object references can crash the entire 
process, as in C++!

This tip shows you how to correctly manage an object's
deallocation in your JNI code.  
 
Let's look at a simple example that uses native code to find the 
maximum value in an array of integers: 

//java code Max.java

import java.util.*;

public class Max {
    public static final int ARRAY_SIZE = 1000;  
    public static int[] arr = initAnArray();
    
    static {
        System.loadLibrary("Max");
    }

    public static int[] initAnArray() {
        int[] arr = new int[ARRAY_SIZE];
        Random rnd = new Random();
        for (int n=0; n<ARRAY_SIZE; n++) {
            arr[n]= rnd.nextInt();
        }
        return arr;
    }
    
    public static int max(int[] nums) {
        int length = nums.length;
        int max = Integer.MIN_VALUE;
        int current = 0;
        for (int n=0; n<length; n++) {
            current = nums[n];
            if (current > max) {
                max = current;
            }
        }
        return max;
    }

    public static native int nativeMax(int[] mins);
    public static native int nativeMaxCritical(int[] mins);
    
    public static void main(String [] args) {
        System.out.println("max=" + max(arr));
        //System.out.println("nativeMax=" + nativeMax(arr));
        //System.out.println("nativeMaxCritical=" + nativeMaxCritical(arr)); 
        }
}

This program calls a max function that is implemented in Java 
code. There are also calls, initially commented out, to two other 
versions of the max function: nativeMax() and nativeMaxCritical(). 
When the calls are uncommented, the functions will need native 
language implementations, such as C++.  

It would be nice if the native code could take advantage of 
certain Java programming language features, such as using 
System.out.println() for logging messages to the console. 
One way to add this feature is to implement the JNI_OnLoad 
method in your C++ library:

//C++ CODE Max.cpp
#include <jni.h>
#include <limits.h>

//cache the methodID and object needed to call System.out.println
static jmethodID midPrintln;
static jobject objOut;

extern "C" {
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) 
{
    JNIEnv* env = 0;
    jclass clsSystem = 0;
    jclass clsPrintStream = 0;
    jfieldID fidOut = 0;
    jstring msg = 0;

    if (JNI_OK != vm->GetEnv((void **)&env, JNI_VERSION_1_2)) {
        return JNI_ERR;
    }

    clsSystem = env->FindClass("java/lang/System");
    if (!clsSystem) return JNI_ERR;

    clsPrintStream = env->FindClass("java/io/PrintStream");
    if (!clsPrintStream) return JNI_ERR;
    
    fidOut = env->GetStaticFieldID(clsSystem, "out", "Ljava/io/PrintStream;");
    if (!fidOut) return JNI_ERR;

    objOut = env->GetStaticObjectField(clsSystem, fidOut);
    if (!objOut) return JNI_ERR;

    midPrintln = env->GetMethodID(clsPrintStream, "println", "(Ljava/lang/String;)V");
    if (!midPrintln) return JNI_ERR;

    msg = env->NewStringUTF("MAX library loaded");
    if (!msg) return JNI_ERR;

    env->CallVoidMethod(objOut, midPrintln, msg);
    return JNI_VERSION_1_2;
}
}

The JNI_OnLoad entry point is called once, when the native library 
is loaded by a call to System.loadLibrary. In this example, the 
line:

    env->CallVoidMethod(objOut, midPrintln, msg);

actually does the work of calling System.out.println. Before this 
line of code can execute, some preparation must take place. The 
calls to FindClass return jclass references to java.lang.System 
(to reach the out field) and java.io.PrintStream (to reach the 
println method). In JNI fields and methods must be accessed by 
first requesting an ID, done here by the GetStaticFieldID and 
GetMethodID methods. Finally, the string to be printed must be 
allocated using the NewStringUTF helper method. Notice that 
midPrintln and objOut are cached in static variables. This helps 
avoid having to do all the preparation work the next time 
System.out.println is used. Cacheing is also an important 
performance optimization in JNI -- you do not want to repeatedly 
look up objects and ids. 

Compile both the Java code and the C++ code into the same 
directory. Then run the program from that directory using the 
command: 

    java -cp . Max
    
You should see the output "MAX library loaded." in your 
System.out.

Although this code seems to work, it does not correctly manage 
object references. Referring back to the code, notice that 
the methods on the JNIEnv* fall into two categories: (1) those 
that return IDs, and (2) those that return some type of object 
reference. You do not need to worry about the IDs because they 
do not represent any special claim on resources. The methods and 
fields are there as long as the class is loaded, whether you use 
them from JNI or not. The object references are more challenging.  
Unless otherwise documented, all JNI methods return local 
references. A local reference is a thread-local, method-local 
handle to a Java object. In other words, you have permission to 
use the object only for the duration of the JNI method, and only 
from the calling thread. This gives the garbage collector 
a well-defined opportunity to collect the object, that is, 
when you return from a method.  

The JNI_OnLoad method above obtains four local references: 
clsSystem, clsPrintStream, objOut, and msg. Each of these 
references is valid only for the duration of the JNI_OnLoad call.  
For clsSystem, clsPrintStream, and msg, this is exactly what you 
want; these objects are only used within the method. Just as in 
the Java programming language, you do not have to worry about 
deallocating these objects. Garbage collection will take care 
of them. However the objOut handle is processed differently. It 
is cached in a static variable for later use. This leads to 
undefined behavior, that is, there is no guarantee that the 
handle is still valid. The following native methods demonstrate 
the problem: 

//make sure these are inside the extern "C" block
JNIEXPORT jint JNICALL Java_Max_nativeMax
  (JNIEnv *env, jclass, jintArray arr)
{
    jstring msg = env->NewStringUTF("nativeMax not implemented yet"); 
    if (!msg) return 0;
    env->CallVoidMethod(objOut, midPrintln, msg);
    return 0;   
}

JNIEXPORT jint JNICALL Java_Max_nativeMaxCritical
  (JNIEnv *env, jclass, jintArray arr)
{
    jstring msg = env->NewStringUTF("nativeMaxCritical not implemented yet"); 
    if (!msg) return 0;
    env->CallVoidMethod(objOut, midPrintln, msg);
    return 0;   
}

In the next tip, these methods will have complete implementations, 
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.  
This is a case where direct access to the data should provide a 
substantial speedup. So you should probably use 
GetIntArrayElements or GetPrimitiveArrayCritical. Here's the code 
for each: 

JNIEXPORT jint JNICALL Java_Max_nativeMax
  (JNIEnv *env, jclass, jintArray arr)
{
    jstring msg = env->NewStringUTF("in nativeMax");
    if (!msg) return 0;
    env->CallVoidMethod(objOut, midPrintln, msg);
    
    jboolean isCopy = JNI_FALSE;
    long* elems = env->GetIntArrayElements(arr, &isCopy);
    if (!elems) return 0;   //exception already pending

    long length = env->GetArrayLength(arr);
    long max = INT_MIN;
    long current = 0;
    for (int n=0; n<length; n++) {
        current = elems[n];
        if (current > max) {
            max = current;
        }
    }
    env->ReleaseIntArrayElements(arr, elems, JNI_ABORT);
    return max;
}

JNIEXPORT jint JNICALL Java_Max_nativeMaxCritical
  (JNIEnv *env, jclass, jintArray arr)
{
    jstring msg = env->NewStringUTF("in nativeMaxCritical");
    if (!msg) return 0;
    env->CallVoidMethod(objOut, midPrintln, msg);
    
    jboolean isCopy = JNI_FALSE;
    long* elems = (long*) env->GetPrimitiveArrayCritical(arr, &isCopy);
    if (!elems) return 0;   //exception already pending

    long length = env->GetArrayLength(arr);
    long max = INT_MIN;
    long current = 0;
    for (int n=0; n<length; n++) {
        current = elems[n];
        if (current > max) {
            max = current;
        }
    }
    env->ReleasePrimitiveArrayCritical(arr, elems, JNI_ABORT);
    return max;
}

Notice that the two versions of the code are almost identical. 
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 
gives the best performance for your application.  

For further information about JNI, see the following publications:

o The Java Native Interface: Programmer's Guide and Specification 
  (Java Series), by Sheng Liang 
  (http://java.sun.com/docs/books/jni/index.html).

o Java Platform Performance Strategies and Tactics (Java Series),
  by Steve Wilson and Jeff Kesselman. 
  (http://java.sun.com/docs/books/performance/). There is a very 
  interesting chapter on JNI performance.
  
.  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .  .

- NOTE
The names on the JDC mailing list are used for internal Sun
Microsystems(tm) purposes only. To remove your name from the list,
see Subscribe/Unsubscribe below.


- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:

jdc-webmaster@sun.com


- SUBSCRIBE/UNSUBSCRIBE
The JDC Tech Tips are sent to you because you elected to subscribe
when you registered as a JDC member. To unsubscribe from JDC email,
go to the following address and enter the email address you wish to
remove from the mailing list:

http://developer.java.sun.com/unsubscribe.html


To become a JDC member and subscribe to this newsletter go to:

http://java.sun.com/jdc/


- ARCHIVES
You'll find the JDC Tech Tips archives at:

http://developer.java.sun.com/developer/TechTips/index.html


- COPYRIGHT
Copyright 2000 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.

This document is protected by copyright. For more information, see:

http://developer.java.sun.com/developer/copyright.html


JDC Tech Tips 
August 1, 2000










