| Contents | Prev | Next | Index | The Java Native Interface Programmer's Guide and Specification |
The JNI exposes instance and array types (such as jobject, jclass, jstring, and jarray) as opaque references. Native code never directly inspects the contents of an opaque reference pointer. Instead it uses JNI functions to access the data structure pointed to by an opaque reference. By only dealing with opaque references, you need not worry about internal object layout that is dependent upon a particular Java virtual machine implementation. You do, however, need to learn more about different kinds of references in the JNI:
In this chapter, we will discuss these issues in detail. Managing JNI references properly is crucial to writing reliable and space-efficient code.
What are local and global references, and how are they different? We will use a series of examples to illustrate local and global references.
Most JNI functions create local references. For example, the JNI function New-Object creates a new instance and returns a local reference to that instance.
A local reference is valid only within the dynamic context of the native method that creates it, and only within that one invocation of the native method. All local references created during the execution of a native method will be freed once the native method returns.
You must not write native methods that store a local reference in a static variable and expect to use the same reference in subsequent invocations. For example, the following program, which is a modified version of the MyNewString function in Section 4.4.1, uses local references incorrectly.
/* This code is illegal */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
jmethodID cid;
jcharArray elemArr;
jstring result;
if (stringClass == NULL) {
stringClass = (*env)->FindClass(env,
"java/lang/String");
if (stringClass == NULL) {
return NULL; /* exception thrown */
}
}
/* It is wrong to use the cached stringClass here,
because it may be invalid. */
cid = (*env)->GetMethodID(env, stringClass,
"<init>", "([C)V");
...
elemArr = (*env)->NewCharArray(env, len);
...
result = (*env)->NewObject(env, stringClass, cid, elemArr);
(*env)->DeleteLocalRef(env, elemArr);
return result;
}
We have elided the lines that are not directly relevant to our discussion here. The goal for caching stringClass in a static variable might have been to eliminate the overhead of repeatedly making the following function call:
FindClass(env, "java/lang/String");
This is not the right approach because FindClass returns a local reference to the java.lang.String class object. To see why this is a problem, suppose that the native method implementation of C.f calls MyNewString:
JNIEXPORT jstring JNICALL
Java_C_f(JNIEnv *env, jobject this)
{
char *c_str = ...;
...
return MyNewString(c_str);
}
After the native method C.f returns, the virtual machine frees all local references created during the execution of Java_C_f. These freed local references include the local reference to the class object stored in the stringClass variable. Future MyNewString calls will then attempt to use an invalid local reference, which could lead to memory corruption or system crashes. A code segment such as the following, for example, makes two consecutive calls to C.f and causes MyNewString to encounter the invalid local reference:
... ... = C.f(); // The first call is perhaps OK. ... = C.f(); // This would use an invalid local reference. ...
There are two ways to invalidate a local reference. As explained before, the virtual machine automatically frees all local references created during the execution of a native method after the native method returns. In addition, programmers may explicitly manage the lifetime of local references using JNI functions such as DeleteLocalRef.
Why do you want to delete local references explicitly if the virtual machine automatically frees them after native methods return? A local reference keeps the referenced object from being garbage collected until the local reference is invalidated. The DeleteLocalRef call in MyNewString, for example, allows the intermediate array object, elemArr, to be garbage collected immediately. Otherwise the virtual machine will only be able to free the elemArr object after the native method that calls MyNewString (such as C.f above) returns.
A local reference may be passed through multiple native functions before it is destroyed. For example, MyNewString returns the string reference created by NewObject. It will then be up to the caller of MyNewString to determine whether to free the local reference returned by MyNewString. In the Java_C_f example, C.f in turn returns the result of MyNewString as the result of the native method call. After the virtual machine receives the local reference from the Java_C_f function, it passes the underlying string object to the caller of C.f and then destroys the local reference that was originally created by the JNI function NewObject.
Local references are also only valid in the thread that creates them. A local reference that is created in one thread cannot be used in another thread. It is a programming error for a native method to store a local reference in a global variable and expect another thread to use the local reference.
You can use a global reference across multiple invocations of a native method. A global reference can be used across multiple threads and remains valid until it is freed by the programmer. Like a local reference, a global reference ensures that the referenced object will not be garbage collected.
Unlike local references, which are created by most JNI functions, global references are created by just one JNI function, NewGlobalRef. The following version of MyNewString illustrates how to use a global reference. We highlight the differences between the code below and the code that mistakenly cached a local reference in the last section:
/* This code is OK */
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jclass stringClass = NULL;
...
if (stringClass == NULL) {
jclass localRefCls =
(*env)->FindClass(env, "java/lang/String");
if (localRefCls == NULL) {
return NULL; /* exception thrown */
}
/* Create a global reference */
stringClass = (*env)->NewGlobalRef(env, localRefCls);
/* The local reference is no longer useful */
(*env)->DeleteLocalRef(env, localRefCls);
/* Is the global reference created successfully? */
if (stringClass == NULL) {
return NULL; /* out of memory exception thrown */
}
}
...
}
The modified version passes the local reference returned from FindClass to NewGlobalRef, which creates a global reference to the java.lang.String class object. We check whether the NewGlobalRef has successfully created stringClass after deleting localRefCls because the local reference localRefCls needs to be deleted in either case.
Weak global references are new in Java 2 SDK release 1.2. They are created using NewGlobalWeakRef and freed using DeleteGlobalWeakRef. Like global references, weak global references remain valid across native method calls and across different threads. Unlike global references, weak global references do not keep the underlying object from being garbage collected.
The MyNewString example has shown how to cache a global reference to the java.lang.String class. The MyNewString example could alternatively use a weak global reference to store the cached java.lang.String class. It does not matter whether we use a global reference or a weak global reference because java.lang.String is a system class and will never be garbage collected.
Weak global references become more useful when a reference cached by the native code must not keep the underlying object from being garbage collected. Suppose, for example, a native method mypkg.MyCls.f needs to cache a reference to the class mypkg.MyCls2. Caching the class in a weak global reference allows the mypkg.MyCls2 class to still be unloaded:
JNIEXPORT void JNICALL
Java_mypkg_MyCls_f(JNIEnv *env, jobject self)
{
static jclass myCls2 = NULL;
if (myCls2 == NULL) {
jclass myCls2Local =
(*env)->FindClass(env, "mypkg/MyCls2");
if (myCls2Local == NULL) {
return; /* can't find class */
}
myCls2 = NewWeakGlobalRef(env, myCls2Local);
if (myCls2 == NULL) {
return; /* out of memory */
}
}
... /* use myCls2 */
}
We assume that MyCls and MyCls2 have the same lifetime. (For example, they may be loaded by the same class loader.) Thus we do not consider the case when MyCls2 is unloaded and later reloaded while MyCls and its native method implementation Java_mypkg_MyCls remain to be in use. If that could happen, we would have to check whether the cached weak reference still points to a live class object or points to a class object that has already been garbage collected. The next section will explain how to perform such checks on weak global references.
Given two local, global, or weak global references, you can check whether they refer to the same object using the IsSameObject function. For example:
(*env)->IsSameObject(env, obj1, obj2)
returns JNI_TRUE (or 1) if obj1 and obj2 refer to the same object, and returns JNI_FALSE (or 0) otherwise.
A NULL reference in JNI refers to the null object in the Java virtual machine. If obj is a local or a global reference, you may use either
(*env)->IsSameObject(env, obj, NULL)
obj == NULL
to determine if obj refers to the null object.
The rules for weak global references are somewhat different. NULL weak references refer to the null object. IsSameObject, however, has special uses for weak global references. You can use IsSameObject to determine whether a non-NULL weak global reference still points to a live object. Suppose wobj is a non-NULL weak global reference. The following call:
(*env)->IsSameObject(env, wobj, NULL)
returns JNI_TRUE if wobj refers to an object that has already been collected, and returns JNI_FALSE if wobj still refers to a live object.
Each JNI reference consumes a certain amount of memory by itself, in addition to the memory taken by the referred object. As a JNI programmer, you should be aware of the number of references that your program will use at a given time. In particular, you should be aware of the upper bound of the number of local references your program can create at any point during its execution, even though these local references will eventually be freed automatically by the virtual machine. Excessive reference creation, however transient, can lead to memory exhaustion.
In most cases, you do not have to worry about freeing local references when implementing a native method. The Java virtual machine frees them for you when the native method returns to the caller. However, there are times when you, the JNI programmer, should explicitly free local references in order to avoid excessive memory usage. Consider the following situations:
for (i = 0; i < len; i++) { jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ... /* process jstr */ (*env)->DeleteLocalRef(env, jstr); }
MyNewString example shown in Section 4.3 illustrates the use of Delete-LocalRef to delete promptly local references in a utility function. Otherwise there will be two local references that remains allocated after each call to the MyNewString function.
DeleteLocalRef beforehand, the garbage collector may be able to free the object referred to by lref when the execution is inside the function lengthy-Computation:/* A native method implementation */ JNIEXPORT void JNICALL Java_pkg_Cls_func(JNIEnv *env, jobject this) { lref = ... /* a large Java object */ ... /* last use of lref */ (*env)->DeleteLocalRef(env, lref); lengthyComputation(); /* may take some time */ return; /* all local refs are freed */ }
Java 2 SDK release 1.2 provides an additional set of functions for managing the lifetime of local references. These functions are EnsureLocalCapacity, New-LocalRef, PushLocalFrame, and PopLocalFrame.
The JNI specification dictates that the virtual machine automatically ensures that each native method can create at least 16 local references. Experience shows that this provides enough capacity for the majority of native methods that do not contain complex interactions with objects in the Java virtual machine. If, however, there is a need to create additional local references, a native method may issue an EnsureLocalCapacity call to make sure that space for a sufficient number of local references is available. For example, a slight variation of a previous example above reserves enough capacity for all local references created during the loop execution if sufficient memory is available:
/* The number of local references to be created is equal to
the length of the array. */
if ((*env)->EnsureLocalCapacity(env, len)) < 0) {
... /* out of memory */
}
for (i = 0; i < len; i++) {
jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
/* DeleteLocalRef is no longer necessary */
}
Of course, the above version is likely to consume more memory that the previous version which promptly deletes local references.
Alternatively, the Push/PopLocalFrame functions allow programmers to create nested scopes of local references. For example, we may also rewrite the same example as follows:
#define N_REFS ... /* the maximum number of local references
used in each iteration */
for (i = 0; i < len; i++) {
if ((*env)->PushLocalFrame(env, N_REFS) < 0) {
... /* out of memory */
}
jstr = (*env)->GetObjectArrayElement(env, arr, i);
... /* process jstr */
(*env)->PopLocalFrame(env, NULL);
}
PushLocalFrame creates a new scope for specific number of local references. PopLocalFrame destroys the topmost scope, freeing all local references in that scope.
The advantage of using the Push/PopLocalFrame functions is that they make it possible to manage the lifetime of local references without having to worry about every single local reference that might be created during execution. In the above example, if the computation that processes jstr creates additional local references, these local references will be freed after PopLocalFrame returns.
The NewLocalRef function is useful when you write utility functions that are expected to return a local reference. We will demonstrate the use of the New-LocalRef function in Section 5.3.
The native code may create local references beyond the default capacity of 16 or the capacity reserved in a PushLocalFrame or EnsureLocalCapacity call. The virtual machine implementation will try to allocate the memory needed for the local reference. There is no guarantee, however, that memory will be available. The virtual machine exits if it fails to allocate the memory. You should reserve enough memory for local references and free local references promptly to avoid such unexpected virtual machine exits.
Java 2 SDK release 1.2 supports a command-line option -verbose:jni. When this option is enabled, the virtual machine implementation reports excessive local reference creation beyond the reserved capacity.
You should call DeleteGlobalRef when your native code no longer needs access to a global reference. If you fail to call this function the Java virtual machine will not garbage collect the corresponding object, even when the object is no longer used anywhere else in the system.
You should call DeleteWeakGlobalRef when your native code no longer needs access to a weak global reference. If you fail to call this function the Java virtual machine will still be able to garbage collect the underlying object, but will not be able to reclaim the memory consumed by the weak global reference itself.
We are now ready to go through the rules for managing JNI references in native code, based on what we have covered in previous sections. The objective is to eliminate unnecessary memory usage and object retention.
There are, in general, two kinds of native code: functions that directly implement native methods and utility functions that are used in arbitrary contexts.
When writing functions that directly implement native methods, you need to be careful about excessive local reference creation in loops and unnecessary local reference creation caused by native methods that do not return. It is acceptable to leave up to 16 local references in use for the virtual machine to delete after the native method returns. Native method calls must not cause global or weak global references to accumulate because global and weak global references are not freed automatically after native methods return.
When writing native utility functions you must be careful not to leak any local references on any execution path throughout the function. Because a utility function may be called repeatedly from an unanticipated context, any unnecessary reference creation may cause memory overflow.
It is acceptable for a utility function to create some global or weak global references for the purpose of caching because only the very first call creates these references.
If a utility function returns a reference, you should make the kind of returned reference part of the function specification. It should not return a local reference some of the time and a global reference at other times. The caller needs to know the type of the reference returned by a utility function in order to manage its own JNI references correctly. For example, the following code calls a utility function GetInfoString repeatedly. We need to know the type of reference returned by GetInfoString to be able to free the returned JNI reference properly after each iteration.
while (JNI_TRUE) {
jstring infoString = GetInfoString(info);
... /* process infoString */
??? /* we need to call DeleteLocalRef, DeleteGlobalRef,
or DeleteWeakGlobalRef depending on the type of
reference returned by GetInfoString. */
}
In Java 2 SDK release 1.2, the NewLocalRef function sometimes is useful to ensure that a utility function always returns a local reference. To illustrate, let us make another (somewhat contrived) change to the MyNewString function. The following version caches a frequently requested string (say, "CommonString") in a global reference:
jstring
MyNewString(JNIEnv *env, jchar *chars, jint len)
{
static jstring result;
/* wstrncmp compares two Unicode strings */
if (wstrncmp("CommonString", chars, len) == 0) {
/* refers to the global ref caching "CommonString" */
static jstring cachedString = NULL;
if (cachedString == NULL) {
/* create cachedString for the first time */
jstring cachedStringLocal = ... ;
/* cache the result in a global reference */
cachedString =
(*env)->NewGlobalRef(env, cachedStringLocal);
}
return (*env)->NewLocalRef(env, cachedString);
}
... /* create the string as a local reference and store in
result as a local reference */
return result;
}
The normal path returns a string as a local reference. As explained before, we must store the cached string in a global reference so that it can be accessed in multiple native method invocations and from multiple threads. The highlighted line creates a new local reference that refers to the same object as the cached global reference. As part of the contract with its callers, MyNewString always returns a local reference.
The Push/PopLocalFrame functions are especially convenient for managing the lifetime of local references. If you called PushLocalFrame on entry to a native function, calling PopLocalFrame before the native function returns ensures that all local references created during native function execution will be freed. The Push/PopLocalFrame functions are efficient. You are strongly encouraged to use them.
If you call PushLocalFrame on function entry, remember to call Pop-LocalFrame in all function exit paths. For example, the following function has one call to PushLocalFrame but needs multiple calls to PopLocalFrame:
jobject f(JNIEnv *env, ...)
{
jobject result;
if ((*env)->PushLocalFrame(env, 10) < 0) {
/* frame not pushed, no PopLocalFrame needed */
return NULL;
}
...
result = ...;
if (...) {
/* remember to pop local frame before return */
result = (*env)->PopLocalFrame(env, result);
return result;
}
...
result = (*env)->PopLocalFrame(env, result);
/* normal return */
return result;
}
Failing to place PopLocalFrame calls properly would lead to undefined behavior, such as virtual machine crashes.
The above example also illustrates why it is sometimes useful to specify the second argument to PopLocalFrame. The result local reference is initially created in the new frame constructed by PushLocalFrame. PopLocalFrame converts its second argument, result, to a new local reference in the previous frame before popping the topmost frame.
| Contents | Prev | Next | Index | The Java Native Interface Programmer's Guide and Specification |
Copyright © 2002 Sun Microsystems, Inc.
All rights reserved
Please send any comments or corrections to jni@java.sun.com