출처 : http://java.sun.com/docs/books/jni/html/fldmeth.html
Chapter 4
Fields and Methods
Now that you know how the JNI lets native code access primitive types and reference types such as strings and arrays, the next step will be to learn how to interact with fields and methods in arbitrary objects. In addition to accessing fields, this includes making calls to methods implemented in the Java programming language from native code, commonly known as performing callbacks from native code.
We will begin by introducing the JNI functions that support field access and method callbacks. Later in this chapter we will discuss how to make such operations more efficient by using a simple but effective caching technique. In the last section, we will discuss the performance characteristics of calling native methods as well as accessing fields and calling methods from native code.
4.1 Accessing Fields
The Java programming language supports two kinds of fields. Each instance of a class has its own copy of the instance fields of the class, whereas all instances of a class share thestatic fields of the class.
The JNI provides functions that native code can use to get and set instance fields in objects and static fields in classes. Let us first look at an example program that illustrates how to access instance fields from a native method implementation.
class InstanceFieldAccess {
private String s;
private native void accessField();
public static void main(String args[]) {
InstanceFieldAccess c = new InstanceFieldAccess();
c.s = "abc";
c.accessField();
System.out.println("In Java:");
System.out.println(" c.s = \"" + c.s + "\"");
}
static {
System.loadLibrary("InstanceFieldAccess");
}
}
The InstanceFieldAccess
class defines an instance field s
. The main
method creates an object, sets the instance field, and then calls the native method InstanceFieldAccess.accessField
. As we will see shortly, the native method prints out the existing value of the instance field and then sets the field to a new value. The program prints the field value again after the native method returns, demonstrating that the field value has indeed changed.
Here is the implementation of the InstanceFieldAccess
.accessField
native method.
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jstring jstr;
const char *str;
/* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the instance field s in cls */
fid = (*env)->GetFieldID(env, cls, "s",
"Ljava/lang/String;");
if (fid == NULL) {
return; /* failed to find the field */
}
/* Read the instance field s */
jstr = (*env)->GetObjectField(env, obj, fid);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
/* Create a new string and overwrite the instance field */
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid, jstr);
}
Running the InstanceFieldAccess
class with the InstanceFieldAccess
native library produces the following output:
In C:
c.s = "abc"
In Java:
c.s = "123"
4.1.1 Procedure for Accessing an Instance Field
To access an instance field, the native method follows a two-step process. First, it calls GetFieldID
to obtain the field ID from the class reference, field name, and field descriptor:
fid = (*env)->GetFieldID(env, cls, "s", "Ljava/lang/String;");
The example code obtains the class reference cls
by calling GetObjectClass
on the instance reference obj
, which is passed as the second argument to the native method implementation.
Once you have obtained the field ID, you can pass the object reference and the field ID to the appropriate instance field access function:
jstr = (*env)->GetObjectField(env, obj, fid);
Because strings and arrays are special kinds of objects, we use GetObjectField
to access the instance field that is a string. Besides Get/SetObjectField
, the JNI also supports other functions such as GetIntField
and SetFloatField
for accessing instance fields of primitive types.
4.1.2 Field Descriptors
You might have noticed that in the previous section we used a specially encoded C string "Ljava/lang/String;"
to represent a field type in the Java programming language. These C strings are called JNI field descriptors.
The content of the string is determined by the declared type of the field. For example, you represent an int
field with "I"
, a float
field with "F"
, a double
field with "D"
, a boolean
field with"Z"
, and so on.
The descriptor for a reference type, such as java.lang.String
, begins with the letter L
, is followed by the JNI class descriptor (§3.3.5), and is terminated by a semicolon. The "."
separators in fully qualified class names are changed to "/"
in JNI class descriptors. Thus, you form the field descriptor for a field with type java.lang.String
as follows:
"Ljava/lang/String;"
Descriptors for array types consist of the "["
character, followed by the descriptor of the component type of the array. For example, "[I"
is the descriptor for the int[]
field type. Section 12.3.3 contains the details of field descriptors and their matching types in the Java programming language.
You can use the javap
tool (shipped with JDK or Java 2 SDK releases) to generate the field descriptors from class files. Normally javap
prints out the method and field types in a given class. If you specify the -s
option (and the -p
option for exposing private members), javap
prints JNI descriptors instead:
javap -s -p InstanceFieldAccess
This gives you output containing the JNI descriptors for the field s
:
...
s Ljava/lang/String;
...
Using the javap
tool helps eliminate mistakes that can occur from deriving JNI descriptor strings by hand.
4.1.3 Accessing Static Fields
Accessing static fields is similar to accessing instance fields. Let us look at a minor variation of the InstanceFieldAccess
example:
class StaticFielcdAccess {
private static int si;
private native void accessField();
public static void main(String args[]) {
StaticFieldAccess c = new StaticFieldAccess();
StaticFieldAccess.si = 100;
c.accessField();
System.out.println("In Java:");
System.out.println(" StaticFieldAccess.si = " + si);
}
static {
System.loadLibrary("StaticFieldAccess");
}
}
The StaticFieldAccess
class contains a static integer field si
. The Static-FieldAccess.main
method creates an object, initializes the static field, and then calls the native methodStaticFieldAccess.accessField
. As we will see shortly, the native method prints out the existing value of the static field and then sets the field to a new value. To verify that the field has indeed changed, the program prints the static field value again after the native method returns.
Here is the implementation of the StaticFieldAccess.accessField
native method.
JNIEXPORT void JNICALL
Java_StaticFieldAccess_accessField(JNIEnv *env, jobject obj)
{
jfieldID fid; /* store the field ID */
jint si;
/* Get a reference to obj's class */
jclass cls = (*env)->GetObjectClass(env, obj);
printf("In C:\n");
/* Look for the static field si in cls */
fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
if (fid == NULL) {
return; /* field not found */
}
/* Access the static field si */
si = (*env)->GetStaticIntField(env, cls, fid);
printf(" StaticFieldAccess.si = %d\n", si);
(*env)->SetStaticIntField(env, cls, fid, 200);
}
Running the program with the native library produces the following output:
In C:
StaticFieldAccess.si = 100
In Java:
StaticFieldAccess.si = 200
There are two differences between how you access a static field and how you access an instance field:
- You call
GetStaticFieldID
for static fields, as opposed to GetFieldID
for instance fields. GetStaticFieldID
and GetFieldID
have the same return type jfieldID
.
- Once you have obtained the static field ID, you pass the class reference, as opposed to an object reference, to the appropriate static field access function.
4.2 Calling Methods
There are several kinds of methods in the Java programming language. Instance methods
must be invoked on a specific instance of a class, whereas static methods
may be invoked independent of any instance. We will defer the discussion of constructors to the next section.
The JNI supports a complete set of functions that allow you to perform callbacks from native code. The example program below contains a native method that in turn calls an instance method implemented in the Java programming language.
class InstanceMethodCall {
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall c = new InstanceMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall");
}
}
Here is the implementation of the native method:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid =
(*env)->GetMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallVoidMethod(env, obj, mid);
}
Running the above program produces the following output:
In C
In Java
4.2.1 Calling Instance Methods
The Java_
InstanceMethodCall
_nativeMethod
implementation illustrates the two steps required to call an instance method:
- The native method first calls the JNI function
GetMethodID
. GetMethodID
performs a lookup for the method in the given class. The lookup is based on the name and type descriptor of the method. If the method does not exist, GetMethodID
returns NULL
. At this point, an immediate return from the native method causes a NoSuchMethodError
to be thrown in the code that called InstanceMethodCall.nativeMethod
.
- The native method then calls
CallVoidMethod
. CallVoidMethod
invokes an instance method that has the return type void
. You pass the object, the method ID, and the actual arguments (though in the above example there are none) to CallVoidMethod
.
Besides the CallVoidMethod
function, the JNI also supports method invocation functions with other return types. For example, if the method you called back returned a value of type int
, then your native method would use CallIntMethod
. Similarly, you can use CallObjectMethod
to call methods that return objects, which include java.lang.String
instances and arrays.
You can use Call<Type>Method
family of functions to invoke interface methods as well. You must derive the method ID from the interface type. The following code segment, for example, invokes the Runnable.run
method on a java.lang.Thread
instance:
jobject thd = ...; /* a java.lang.Thread instance */
jmethodID mid;
jclass runnableIntf =
(*env)->FindClass(env, "java/lang/Runnable");
if (runnableIntf == NULL) {
... /* error handling */
}
mid = (*env)->GetMethodID(env, runnableIntf, "run", "()V");
if (mid == NULL) {
... /* error handling */
}
(*env)->CallVoidMethod(env, thd, mid);
... /* check for possible exceptions */
We have seen in Section 3.3.5 that the FindClass
function returns a reference to a named class. Here we also use it to obtain a reference to a named interface.
4.2.2 Forming the Method Descriptor
The JNI uses descriptor strings to denote method types in a way similar to how it denotes field types. A method descriptor combines the argument types and the return type of a method. The argument types appear first and are surrounded by one pair of parentheses. Argument types are listed in the order in which they appear in the method declaration. There are no separators between multiple argument types. If a method takes no arguments, this is represented with an empty pair of parentheses. Place the method's return type immediately after the right closing parenthesis for the argument types.
For example, "(I)V
" denotes a method that takes one argument of type int
and has return type void
. "()D
" denotes a method that takes no arguments and returns a double
. Do not let C function prototypes such as "int f(void)
" mislead you to thinking that "(V)I
" is a valid method descriptor. Use "()I
" instead.
Method descriptors may involve class descriptors (§12.3.2). For example, the method:
native private String getLine(String);
has the following descriptor:
"(Ljava/lang/String;)Ljava/lang/String;"
Descriptors for array types begin with the "[
" character, followed by the descriptor of the array element type. For example, the method descriptor of:
public static void main(String[] args);
is as follows:
"([Ljava/lang/String;)V"
Section 12.3.4 gives a complete description of how to form JNI method descriptors. You can use the javap
tool to print out JNI method descriptors. For example, by running:
javap -s -p InstanceMethodCall
you obtain the following output:
...
private callback ()V
public static main ([Ljava/lang/String;)V
private native nativeMethod ()V
...
The -s
flag informs javap
to output JNI descriptor strings rather than types as they appear in the Java programming language. The -p
flag causes javap
to include information about the private members of the class in its output.
4.2.3 Calling Static Methods
The previous example demonstrates how native code calls an instance method. Similarly, you can perform callbacks to static methods from native code by following these steps:
- Obtain the method ID using
GetStaticMethodID
, as opposed to GetMethodID
.
- Pass the class, method ID, and arguments to one of the family of static method invocation functions:
CallStaticVoidMethod
, CallStaticBooleanMethod
, and so on.
There is a key difference between the functions that allow you to call static methods and the functions that allow you to call instance methods. The former takes a class reference as the second argument, whereas the latter takes an object reference as the second argument. For example, you pass the class reference to CallStaticVoidMethod
, but pass an object reference to CallVoidMethod
.
At the Java programming language level, you can invoke a static method f
in class Cls
using two alternative syntaxes: either Cls.f o
r obj.f
where obj
is an instance of Cls
. (The latter is the recommended programming style, however.) In the JNI, you must always specify the class reference when issuing static method calls from native code.
Let us look at an example that makes a callback to a static method from native code. It is a slight variation of the earlier InstanceMethodCall
example:
class StaticMethodCall {
private native void nativeMethod();
private static void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
StaticMethodCall c = new StaticMethodCall();
c.nativeMethod();
}
static {
System.loadLibrary("StaticMethodCall");
}
}
Here is the implementation of the native method:
JNIEXPORT void JNICALL
Java_StaticMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
jclass cls = (*env)->GetObjectClass(env, obj);
jmethodID mid =
(*env)->GetStaticMethodID(env, cls, "callback", "()V");
if (mid == NULL) {
return; /* method not found */
}
printf("In C\n");
(*env)->CallStaticVoidMethod(env, cls, mid);
}
Make sure that you pass cls
(highlighted in bold), as opposed to obj
, to CallStaticVoidMethod
. Running the above program produces the following expected output:
In C
In Java
4.2.4 Calling Instance Methods of a Superclass
You can call instance methods which were defined in a superclass but that have been overridden in the class to which the object belongs. The JNI provides a set ofCallNonvirtual<Type>Method functions for this purpose. To call a instance method defined in a superclass, you do the following:
- Obtain the method ID from a reference to the superclass using
GetMethodID
, as opposed to GetStaticMethodID
.
- Pass the object, superclass, method ID, and arguments to one of the family of nonvirtual invocation functions, such as
CallNonvirtualVoidMethod
, CallNonvirtualBooleanMethod
, and so on.
It is relatively rare that you will need to invoke the instance methods of a superclass. This facility is similar to calling an overridden superclass method, say f
, using the following construct in the Java programming language:
super.f();
CallNonvirtualVoidMethod
can also be used to invoke constructors, as the next section will illustrate.
4.3 Invoking Constructors
In the JNI, constructors may be invoked following steps similar to those used for calling instance methods. To obtain the method ID of a constructor, pass "<init>"
as the method name and "V"
as the return type in the method descriptor. You can then invoke the constructor by passing the method ID to JNI functions such as NewObject
. The following code implements the equivalent functionality of the JNI function NewString
, which constructs a java.lang.String
object from the Unicode characters stored in a C buffer:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jmethodID cid;
jcharArray elemArr;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Get the method ID for the String(char[]) constructor */
cid = (*env)->GetMethodID(env, stringClass,
"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
This function is complex enough to deserve careful explanation. First, FindClass
returns a reference to the java.lang.String
class. Next, GetMethodID
returns the method ID for the string constructor, String(char[] chars)
. We then call NewCharArray
to allocate a character array that holds all the string elements. The JNI function NewObject
invokes the constructor specified by the method ID. The NewObject
function takes as arguments the reference to the class to be constructed, the method ID of the constructor, and the arguments that need to be passed to the constructor.
The DeleteLocalRef
call allows the virtual machine to free the resources used by local references elemArr
and stringClass
. Section 5.2.1 will provide a detailed description of when and why you should call DeleteLocalRef
.
Strings are objects. This example highlights the point further. The example also leads to a question, however. Given that we can implement equivalent functionality using other JNI functions, why does the JNI provide built-in functions such as NewString
? The reason is that the built-in string functions are far more efficient than calling the java.lang.String
API from native code. String is the most frequently used type of objects, one that deserves special support in the JNI.
It is also possible to invoke constructors using the CallNonvirtualVoidMethod
function. In this case, the native code must first create an uninitialized object by calling the AllocObject
function. The single NewObject
call above:
result = (*env)->NewObject(env, stringClass, cid, elemArr);
may be replaced by an AllocObject
call followed by a CallNonvirtualVoidMethod
call:
result = (*env)->AllocObject(env, stringClass);
if (result) {
(*env)->CallNonvirtualVoidMethod(env, result, stringClass,
cid, elemArr);
/* we need to check for possible exceptions */
if ((*env)->ExceptionCheck(env)) {
(*env)->DeleteLocalRef(env, result);
result = NULL;
}
}
AllocObject
creates an uninitialized object, and must be used with care so that a constructor is called at most once on each object. The native code should not invoke a constructor on the same object multiple times.
Occasionally you may find it useful to allocate an uninitialized object first and call the constructor sometime later. In most cases, however, you should use NewObject
and avoid the more error-prone AllocObject/CallNonvirtualVoidMethod
pair.
4.4 Caching Field and Method IDs
Obtaining field and method IDs requires symbolic lookups based on the name and descriptor of the field or method. Symbolic lookups are relatively expensive. In this section, we introduce a technique that can be used to reduce this overhead.
The idea is to compute field and method IDs and cache them for repeated uses later. There are two ways to cache field and method IDs, depending upon whether caching is performed at the point of use of the field or method ID, or in the static initializer of the class that defines the field or method.
4.4.1 Caching at the Point of Use
Field and method IDs may be cached at the point where native code accesses the field values or performs method callbacks. The following implementation of theJava_InstanceFieldAccess_accessField
function caches the field ID in static variables so that it need not be recomputed upon each invocation of the InstanceFieldAccess.accessField
method.
JNIEXPORT void JNICALL
Java_InstanceFieldAccess_accessField(JNIEnv *env, jobject obj)
{
static jfieldID fid_s = NULL; /* cached field ID for s */
jclass cls = (*env)->GetObjectClass(env, obj);
jstring jstr;
const char *str;
if (fid_s == NULL) {
fid_s = (*env)->GetFieldID(env, cls, "s",
"Ljava/lang/String;");
if (fid_s == NULL) {
return; /* exception already thrown */
}
}
printf("In C:\n");
jstr = (*env)->GetObjectField(env, obj, fid_s);
str = (*env)->GetStringUTFChars(env, jstr, NULL);
if (str == NULL) {
return; /* out of memory */
}
printf(" c.s = \"%s\"\n", str);
(*env)->ReleaseStringUTFChars(env, jstr, str);
jstr = (*env)->NewStringUTF(env, "123");
if (jstr == NULL) {
return; /* out of memory */
}
(*env)->SetObjectField(env, obj, fid_s, jstr);
}
The highlighted static variable fid_s
stores the precomputed field ID for InstanceFieldAccess.s
. The static variable is initialized to NULL
. When the InstanceFieldAccess.accessField
method is called for the first time, it computes the field ID and caches it in the static variable for later use.
You may notice that there is an obvious race condition in the above code. Multiple threads may call the InstanceFieldAccess.accessField
method at the same time and compute the same field ID concurrently. One thread may overwrite the static variable fid_s
computed by another thread. Luckily, although this race condition leads to duplicated work in multiple threads, it is otherwise harmless. The field IDs computed by multiple threads for the same field in the same class will necessarily be the same.
Following the same idea, we may also cache the method ID for the java.lang.String
constructor in the earlier MyNewString
example:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
jclass stringClass;
jcharArray elemArr;
static jmethodID
cid = NULL;
jstring result;
stringClass = (*env)->FindClass(env, "java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
/* Note that cid is a static variable */
if (cid == NULL) {
/* Get the method ID for the String constructor */
cid = (*env)->GetMethodID(env, stringClass,
"<init>", "([C)V");
if (cid == NULL) {
return NULL; /* exception thrown */
}
}
/* Create a char[] that holds the string characters */
elemArr = (*env)->NewCharArray(env, len);
if (elemArr == NULL) {
return NULL; /* exception thrown */
}
(*env)->SetCharArrayRegion(env, elemArr, 0, len, chars);
/* Construct a java.lang.String object */
result = (*env)->NewObject(env, stringClass, cid, elemArr);
/* Free local references */
(*env)->DeleteLocalRef(env, elemArr);
(*env)->DeleteLocalRef(env, stringClass);
return result;
}
We compute the method ID for the java.lang.String
constructor when MyNewString
is called for the first time. The highlighted static variable cid
caches the result.
4.4.2 Caching in the Defining Class's Initializer
When we cache a field or method ID at the point of use we must introduce a check to detect whether the IDs have already been cached. Not only does this approach incur a small performance impact on the "fast path" when the IDs have already been cached, but it could lead to duplication of caching and checking as well. For example, if multiple native methods all require access to the same field, then they all need a check to compute and cache the corresponding field ID.
In many situations it is more convenient to initialize the field and method IDs required by a native method before the application can have a chance to invoke the native method. The virtual machine always executes the static initializer of a class before it invokes any of the methods in that class. Thus a suitable place for computing and caching field or method IDs is in the static initializer of the class that defines the fields or methods.
For example, to cache the method ID for InstanceMethodCall.callback
we introduce a new native method initIDs
, called from the static initializer of the InstanceMethodCall
class:
class InstanceMethodCall
{
private static native void initIDs();
private native void nativeMethod();
private void callback() {
System.out.println("In Java");
}
public static void main(String args[]) {
InstanceMethodCall
c = new InstanceMethodCall
();
c.nativeMethod();
}
static {
System.loadLibrary("InstanceMethodCall
");
initIDs();
}
}
Compared to the original code in Section 4.2, the above program contains two extra lines (highlighted in bold font). The implementation of initIDs
simply computes and caches the method ID for InstanceMethodCall.callback
:
jmethodID MID_InstanceMethodCall
_callback;
JNIEXPORT void JNICALL
Java_InstanceMethodCall
_initIDs(JNIEnv *env, jclass cls)
{
MID_InstanceMethodCall
_callback =
(*env)->GetMethodID(env, cls, "callback", "()V");
}
The virtual machine runs the static initializer, and in turn calls the initIDs
method, before executing any other methods (such as nativeMethod
or main
) in the InstanceMethodCall
class. With the method ID is already cached in a global variable, the native implementation of InstanceMethodCall.nativeMethod
no longer needs to perform a symbolic lookup:
JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj)
{
printf("In C\n");
(*env)->CallVoidMethod(env, obj,
MID_InstanceMethodCall
_callback);
}
4.4.3 Comparison between the Two Approaches to Caching IDs
Caching IDs at the point of use is the reasonable solution if the JNI programmer does not have control over the source of the class that defines the field or method. For example, in the MyNewString
example, we cannot inject a custom initIDs
native method into the java.lang.String
class in order to precompute and cache the method ID for the java.lang.String
constructor.
Caching at the point of use has a number of disadvantages when compared with caching in the static initializer of the defining class.
- As explained before, caching at the point of use requires a check in the execution fast path and may also require duplicated checks and initialization of the same field or method ID.
- Method and field IDs are only valid until the class is unloaded. If you cache field and method IDs at the point of use you must make sure that the defining class will not be unloaded and reloaded as long as the native code still relies on the value of the cached ID. (The next chapter will show how you can keep a class from being unloaded by creating a reference to that class using the JNI.) On the other hand, if caching is done in the static initializer of the defining class, the cached IDs will automatically be recalculated when the class is unloaded and later reloaded.
Thus, where feasible, it is preferable to cache field and method IDs in the static initializer of their defining classes.
4.5 Performance of JNI Field and Method Operations
After learning how to cache field and method IDs to enhance performance, you might wonder: What are the performance characteristics of accessing fields and calling methods using the JNI? How does the cost of performing a callback from native code (a native/Java callback) compare with the cost of calling a native method (a Java/native call), and with the cost of calling a regular method (a Java/Java call)?
The answer to this question no doubt depends on how efficiently the underlying virtual machine implements the JNI. It is thus impossible to give an exact account of performance characteristics that is guaranteed to apply to a wide variety of virtual machine implementations. Instead, we will analyze the inherent cost of native method calls and JNI field and method operations and provide a general performance guideline for JNI programmers and implementors.
Let us start by comparing the cost of Java/native calls with the cost of Java/Java calls. Java/native calls are potentially slower than Java/Java calls for the following reasons:
- Native methods most likely follow a different calling convention than that used by Java/Java calls inside the Java virtual machine implementation. As a result, the virtual machine must perform additional operations to build arguments and set up the stack frame before jumping to a native method entry point.
- It is common for the virtual machine to inline method calls. Inlining Java/native calls is a lot harder than inlining Java/Java calls.
We estimate that a typical virtual machine may execute a Java/native call roughly two to three times slower than it executes a Java/Java call. Because a Java/Java call takes just a few cycles, the added overhead will be negligible unless the native method performs trivial operations. It is also possible to build virtual machine implementations with Java/native call performance close or equal to that of Java/Java calls. (Such virtual machine implementations, for example, may adopt the JNI calling convention as the internal Java/Java calling convention.)
The performance characteristics of a native/Java callback is technically similar to a Java/native call. In theory, the overhead of native/Java callbacks could also be within two to three times of Java/Java calls. In practice, however, native/Java callbacks are relatively infrequent. Virtual machine implementations do not usually optimize the performance of callbacks. At the time of this writing many production virtual machine implementations are such that the overhead of a native/Java callback can be as much as ten times higher than a Java/Java call.
The overhead of field access using the JNI lies in the cost of calling through the JNIEnv
. Rather than directly dereferencing objects, the native code has to perform a C function call which in turn dereferences the object. The function call is necessary because it isolates the native code from the internal object representation maintained by the virtual machine implementation. The JNI field access overhead is typically negligible because a function call takes only a few cycles.