Caching Field and Method IDs

Một phần của tài liệu The java™ native interface (Trang 67 - 72)

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 the

Java_InstanceFieldAccess_accessFieldfunction caches the field ID in static variables so that it need not be recomputed upon each invocation of the

InstanceFieldAccess.accessField method.

4.4.1 Caching at the Point of Use FIELDS AND METHODS 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 com- putes 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 over- write the static variablefid_scomputed by another thread. Luckily, although this race condition leads to duplicated work in multiple threads, it is otherwise harm- less. The field IDs computed by multiple threads for the same field in the same class will necessarily be the same.

FIELDS AND METHODS Caching at the Point of Use 4.4.1 Following the same idea, we may also cache the method ID for the

java.lang.String constructor in the earlierMyNewString 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

MyNewStringis called for the first time. The highlighted static variablecidcaches the result.

4.4.2 Caching in the Defining Class’s Initializer FIELDS AND METHODS

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 com- puting 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 forInstanceMethodCall.callbackwe 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 forInstanceMethodCall.callback:

jmethodID MID_InstanceMethodCall_callback;

JNIEXPORT void JNICALL

Java_InstanceMethodCall_initIDs(JNIEnv *env, jclass cls) {

MID_InstanceMethodCall_callback =

(*env)->GetMethodID(env, cls, "callback", "()V");

}

FIELDS AND METHODS Comparison between the Two Approaches to Caching IDs 4.4.3 The virtual machine runs the static initializer, and in turn calls the initIDs

method, before executing any other methods (such asnativeMethodormain) in theInstanceMethodCallclass. 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 theMyNewString example, we cannot inject a custom initIDs

native method into thejava.lang.Stringclass in order to precompute and cache the method ID for thejava.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 execu- tion 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 defin- ing 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.

Một phần của tài liệu The java™ native interface (Trang 67 - 72)

Tải bản đầy đủ (PDF)

(318 trang)