Chapter 5 local and global references

JNI exposes instances and array types (such as Jobject, JClass, JString, and jarray) as opaque references. Native code cannot directly examine the contents of opaque reference Pointers. Instead, the JNI function is used to get the data structure to which the opaque reference points. By handling opaque references, you don’t have to worry about the layout of internal object data structures that depend on a particular Java VIRTUAL machine. However, in JNI, you need to know more about the different types of references:

  • JNI supports three types of transparent references: local, global, and weak global
  • Local and global references have different lifecycles. Local references are automatically reclaimed, while global and weak global references persist until the programmer releases them
  • A local or global reference keeps the referenced object from being garbage collected. But a weak global reference allows the referenced object to be garbage collected.
  • Not all references can be used in all contexts. For example, it is illegal to use a local reference in native code after the creation of a reference returns. In this chapter, we will discuss these issues in detail. Proper management of JNI references is critical to writing reliable and space-saving code.

5.1 Local and Global References

What are local and global references, and how are they different? We will use a series of examples to illustrate local and global references.

5.1.1 Local Reference

Most JNI methods create local references. For example, the JNI method NewObject creates a new instance object and returns a local reference to that object.

A local reference is valid only in the dynamic context of the local method that created it, and only in one call of that method. All local references created during the execution of the local method will be released when the local method returns.

You cannot store local references in static variables in local methods and use the same references in subsequent calls. The following code, for example, is a modified version of the MyNewString method from Section 4.4.4, where an incorrect local reference is used.

/* 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; }}/* 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;
}
Copy the code

Rows that are not directly related to what we are going to discuss have been eliminated. The purpose of caching stringClass in static variables may be to eliminate the overhead of repeating function calls like this:

FindClass(env, "java/lang/String");
Copy the code

This is not the correct method because FindClass returns a local reference to a java.lang.String class object. Here’s why this is a problem, assuming that the C.f local method implementation calls MyNewString:

JNIEXPORT jstring JNICALL Java_C_f(JNIEnv *env, jobject this) {
    char*c_str = ... ; .return MyNewString(c_str);
}
Copy the code

After the local method C.f call returns, the virtual machine releases all local references created during the Java_C_f run. These freed local references include local references to class objects stored in stringClass variables. Later, the MyNewString call will attempt to use an invalid local reference, which can cause memory corruption or a system crash. For example, the following code snippet makes two consecutive calls to C.f and causes MyNewString to encounter an invalid local reference:

. . = C.f();// The first call is perhaps OK.. = C.f();// This would use an invalid local reference..Copy the code

There are two ways to invalidate a local method. As mentioned earlier, when the local method returns, the virtual machine automatically releases all local references created during the execution of the local method. In addition, programmers may want to use JNI functions such as DeleteLocalRef to display the life cycle of managed local references.

If the virtual machine is freed automatically when the local method returns, why do you need to show that the local reference is freed? Local references prevent referenced objects from being collected by the garbage collector until local references are invalid. For example, the DeleteLocalRef call in MyNewString allows the array object elemArr to be immediately collected by the garbage collector. Otherwise, the VIRTUAL machine will only release the elemArr object when the local method called by MyNewString returns (for example, C.f above).

A local reference may be passed to multiple local methods before it is destroyed. For example, the MyNewString method returns a string reference created by NewObject. It is then up to the caller of MyNewString to decide whether to free the local reference returned by MyNewString. In the Java_C_f example, C.f returns the result of MyNewString as the result of a local method call. After the virtual machine receives the local reference from JAVA_C_f, it gives the underlying string object to the caller of C.F and then destroys the local reference originally created by JNI function NewObject, and then destroys the local reference originally created by JNI function NewObject.

A reference to is, of course, only valid in the thread that created it. Local references created in one thread cannot be used in other threads. It is a programming error to store a local reference in a global variable in a local method and expect it to be used in another thread.

5.1.2 Global Reference

You can use global variables across multiple local method calls. A global reference can be used across multiple threads and remains valid until the programmer releases it. Like local references, a global reference ensures that referenced objects are not collected by the garbage collector.

Unlike local references, which can be created using most JNI functions, global references can be created using only one JNI method (NewGlobalRef). The next version of MyNewString shows how to use global references. We highlight the difference between the following code and the code that incorrectly caches local references in the previous 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 */
        }

<b>
        /* 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 */
        }
</b>
    }
    ...
}
Copy the code

This modified version passes the local reference returned from FindClass to NewGlobalRef, which creates a global reference to a java.lang.String object. We check if NewGlobalRef successfully creates a stringClass after deleting localRefCls, because in both cases the local reference to localRefCls needs to be removed.

5.1.3 Weak global References

Weak global references were added in the Java 2 JDK 1.2. Weak global references are created by NewGlobalWeakRef and released by DeleteGlobalWeakRef. As with global references, weak global references are still valid for cross-local method calls and cross-thread calls. Unlike global references, weak global references do not prevent the underlying data objects from being collected by the garbage collector.

The MyNewString example shows how to cache a global reference to java.lang.String. The MyNewString example can use weak global references to cache the java.lang.String class. It doesn’t matter whether we use a global or weak global reference, because java.lang.String is a system class and will never be collected by the garbage collector. Weak global references become even more useful when native code cached references do not protect the underlying object from garbage collection. For example, suppose a local method mypks.mycls.f caches class mypks.mycls2. The cache class still allows mypkg.mycls2 to be unloaded in weak global references.

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 */
}
Copy the code

Let’s assume that MyCls and MyCls2 have the same life cycle (for example, they might be loaded through the same classloader). But we did not consider a scenario where MyCls and its native method implementation Java_mypks_MyCls are still in use, and MyCls2 is unloaded and reloaded later. If this happens, we must check whether the weak reference in the cache still points to a living class object or to a class object that has not been collected by the garbage collector. The next section shows how to perform such checks on weak global references.

5.1.4 Reference Comparison

Given two local, global, and weak global references, you can use the IsSameObject method to check whether they refer to the same object. Such as:

(*env)->IsSameObject(env, obj1, obj2)
Copy the code

If obj1 and obj2 refer to the same object, this method returns JNI_TRUE (or 1), otherwise returns JNI_FALSE (or 0).

In JNI, a NULL reference refers to an empty object in the Java virtual machine. If obj is referenced locally or globally, you can use:

(*env)->IsSameObject(env, obj, NULL)
Copy the code

or

obj == NULL
Copy the code

To check whether obj refers to an empty object.

The rules used for weakly referenced objects are a little different. A NULL weak reference references an empty object. However, IsSameObject has special uses for weak global references. You can use IsSameObject to determine whether a non-NULL weak global reference still points to an active object. Assume that wobJ is a non-null weak global reference. The following call:

(*env)->IsSameObject(env, wobj, NULL)
Copy the code

JNI_TRUE is returned if WOBj references an object that has been reclaimed, and JNI_FALSE is returned if WOBJ still references a living object.

5.2 Releasing a Reference

In addition to the memory occupied by the referenced object, each JNI reference itself consumes a certain amount of memory. As a JNI programmer, you should be aware of the number of references your program will use at a given time. In particular, you should be aware of the maximum number of local references your program will allow to be created at some point during execution, even if these local references will eventually be released automatically by the virtual machine. Creating too many references temporarily can cause memory to run out.

5.2.1 Releasing a Local Reference

In most cases, you don’t have to worry too much about releasing local objects when implementing a local method. The Java virtual machine releases local methods for you when they return to the caller. But JNI programmers should sometimes explicitly free local references to avoid excessive memory usage. Consider the situation:

You need to create a lot of local references in a local method call. This can cause JNI internal local reference tables to overflow, so it is a good idea to immediately remove local references that are no longer needed. For example, in the following program section, it is possible for native code to iterate over a large array of strings. After each iteration, the native code should display the release of local references to string elements. As follows:

for (i = 0; i < len; i++) { jstring jstr = (*env)->GetObjectArrayElement(env, arr, i); ./* process jstr */
    (*env)->DeleteLocalRef(env, jstr);
}
Copy the code
  • You want to write a function called from an unknown context. The MyNewString example shown in Section 4.3 (p. 43) illustrates using DeleteLocalRef to quickly remove local references from a function that would otherwise allocate two local references after each call to MyNewString.
  • Your local method will not return. A local method might enter an infinite event scheduling loop, and it would be important to release local references created within the loop so that they do not accumulate indefinitely, causing memory leaks.
  • Your local method accesses a large object, so you need to create a local reference to that object. Native methods can then perform additional calculations before being returned to the caller. A local reference to a large object will prevent the object from being reclaimed by the local garbage collector until the local method returns, even if the object is no longer used in the rest of the local method. For example, in the following snippet, because DeleteLocalRef is explicitly called, the garbage collector might be able to free objects referenced by LREF when longyComputation is executed.
/* 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 */
}
Copy the code

5.2.2 Managing local references in Java 2 JDK 1.2

Java 2 JDK 1.2 provides a set of extrinsic functions to manage the life cycle of native applications. These functions are EnsureLocalCapacity, NewLocalRef, PushLocalFrame, and PopLocalFrame.

The JNI specification states that the virtual machine can automatically ensure that each local method can create at least 16 local references. Experience has shown that this is sufficient for most native methods, except for complex interactions with objects in the virtual machine. If, however, an external local reference needs to be created, the local method may issue a call to EnsureLocalCapacity to ensure that there is enough local reference space. For example, a slight change to the above example provides sufficient capacity for all local references created during the execution of the loop, if enough 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 */
}
Copy the code

Of course, this version consumes more memory than the previous version that immediately removes local references. Alternatively, the Push/PopLocalFrame function allows programmers to create nested ranges of local references. For example, we could 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);
}
Copy the code

PushLocalFrame creates a new scope for a specific number of local references. PopLocalFrame destroys the range beyond which all local references are released. The advantage of using Push/ poplocalFrames is that they can manage the life cycle of local references without worrying about every local reference that might be created during execution. In the above example, if additional local references are created by the JSTR calculation, these local references will be released when PopLocalFrame returns.

The NewLocalRef function is useful when you are writing an instance program that expects to return a local reference. We will demonstrate the use of the NewLocalRef function in Section 5.3.

Local code may create local references that exceed the default capacity of 16 or the capacity reserved in calls to PushLocalFrame or EnsureLocalCapacity. The virtual machine will attempt to allocate memory for local references. However, there is no guarantee that this memory will be available. If memory allocation fails, the VIRTUAL machine exits. You should reserve enough memory for local references and free local references as soon as possible to avoid such unexpected virtual machine exits.

Java 2 JDK 1.2 provides a command-line argument -verbose:jni. If this parameter is enabled, the VM reports the creation of local references that exceed the reserved capacity.

5.2.3 Releasing global Variables

When your local code no longer needs to access a global reference, you should call the DeleteGlobalRef method. If you forget to call this function, the virtual machine will not be able to reclaim the object through the garbage collector, even though the object will never be used anywhere else on the system.

When your native code no longer needs to access a weak global reference, you should call the DeleteWeakGlobalRef method. If you forget to call this function, the Java virtual machine will still be able to collect the underlying object through the garbage collector and will not be able to reclaim the memory occupied by the weak global reference object.

5.3 Reference Management Specifications

We are now ready to deal with the rules for managing JNI references in native code, based on what we have covered in the previous sections. The goal is to eliminate unnecessary memory usage and object retention.

In general, there are two kinds of native code that directly implement functions of native methods and utility functions used in any context.

When writing directly implemented local methods, you need to be careful to avoid creating too many local references in the loop and creating unnecessary local references by local methods that do not return. It is acceptable to leave up to 16 local references to be deleted by the virtual machine after the local method returns. Local method calls cannot result in global or weak global reference accumulation because global references and weak global references are not released when the local method returns. When writing native utility functions, care must be taken not to leak any local references along any execution path throughout the function. Because utility functions can be repeatedly called from unexpected contexts, any unnecessary reference creation can result in memory overruns.

  • When a function that returns a primitive type is called, it has no additional local, global, or weak global reference cumulative side effects.
  • When a function that returns a reference type is called, it cannot have an additional accumulation of local, global, or weak global references unless the reference is treated as the return value. For caching purposes, it is acceptable for a function to create some global or weak global references, since these references are created only on the first call.

If a function returns a reference, you should use the return reference section of the function specification. He should not return a local reference at some times and a global reference at other times. Callers need to know the return type of the function in order to properly manage their OWN JNI references. For example, the following code repeatedly calls a function GetInfoString. We need to know the type of reference returned by GetInfoString so that we can properly release the returned JNI references 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. */
}
Copy the code

In Java 2 JDK 1.2, the NewLocalRef function is often used to ensure that a function returns a local reference. To illustrate, let’s make another (somewhat contricontrive) change to the MyNewString function. The following version caches frequently requested strings (such as “CommonString”) in global references:

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;
}
Copy the code

The normal code path returns a string as a local reference. As explained earlier, we must store cached strings in a global reference that can be accessed by multiple local methods and multiple threads. The line in bold creates a new reference object that references the same object cached in the global reference. As part of its caller contract, MyNewString often returns a local reference.

The Push/PopLocalFrame method is very handy for managing the declaration cycle of local references. If you call PushLocalFrame at the entry of a local method, you need to call PopLocalFrame before the local method returns to ensure that all local references created during the execution of the local method are recycled. The Push/PopLocalFrame function is very efficient. You are strongly advised to use them.

If you called PushLocalFrame at the start of the function, remember to call PopLocalFrame on all exit paths of the program. For example, the following program has one call to PushLocalFrame but requires multiple PopLocalFrame calls.

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);
        returnresult; }... result = (*env)->PopLocalFrame(env, result);/* normal return */ return result;
}
Copy the code

Incorrectly placing PopLocalFrame calls back causes uncertain behavior, such as causing the virtual machine to crash.

The above example also shows why it is sometimes useful to specify the second parameter to PopLocalFrame. The result local reference was originally created in the new frame constructed by PushLocalFrame. PopLocalFrame takes it as the second argument, result converts to the new local reference in the previous one, and pops the topmost frame.

Chapter VI Anomalies

We’ve come across a lot of errors in our native code that we need to check for after executing JNI methods. This chapter describes how native code detects and fixes these error conditions.

We will focus on errors that occur as a result of JNI function calls, rather than arbitrary errors that occur in native code. If a local method makes an operating system call, you only need to follow the documentation to check for possible errors in the system call. On the other hand, if a local method calls back to a Java API method, you must follow the steps described in this chapter to properly check and fix exceptions that may occur during method execution.

6.1 an overview of the

We introduce JNI exception handlers through a series of examples.

6.1.1 Caching and throwing exceptions in native code

The following program shows how to define a local method that throws an exception. The CatchThrow class defines a doIT method and shows that it will throw an IllegalArgumentException:

class CatchThrow {
    private native void doit(a) throws IllegalArgumentException;
    private void callback(a) throws NullPointerException {
        throw new NullPointerException("CatchThrow.callback");
    }

    public static void main(String args[]) {
        CatchThrow c = new CatchThrow();
        try {
            c.doit();
        } catch (Exception e) {
            System.out.println("In Java:\n\t"+ e); }}static {
        System.loadLibrary("CatchThrow"); }}Copy the code

The catchthrow.main method calls the local method doit, which is implemented as follows:

JNIEXPORT void JNICALL Java_CatchThrow_doit(JNIEnv *env, jobject obj) {
    jthrowable exc;
    jclass cls = (*env)->GetObjectClass(env, obj);
    jmethodID mid = (*env)->GetMethodID(env, cls, "callback"."()V");
    if (mid == NULL) {
        return;
    }
    (*env)->CallVoidMethod(env, obj, mid);
    exc = (*env)->ExceptionOccurred(env);
    if (exc) {
        /* We don't do much with the exception, except that we print a debug message for it, clear it, and throw a new exception. */
        jclass newExcCls;
        (*env)->ExceptionDescribe(env);
        (*env)->ExceptionClear(env);
        newExcCls = (*env)->FindClass(env,"java/lang/IllegalArgumentException");
        if (newExcCls == NULL) {
            /* Unable to find the exception class, give up. */
            return;
        }
    (*env)->ThrowNew(env, newExcCls, "thrown from C code"); }}Copy the code

Running this program with a local library produces the following output:

java.lang.NullPointerException:
    at CatchThrow.callback(CatchThrow.java)
    at CatchThrow.doit(Native Method)
    at CatchThrow.main(CatchThrow.java)
In Java:
    java.lang.IllegalArgumentException: thrown from C code
Copy the code

This callback method throws a NullPointerException. After the CallVoidMethod returns control to the local method, the local code detects this exception through the JNI method ExceptionOccurred. In our example, when an exception is detected, the local method prints a descriptive message about the exception by calling ExceptionDescribe, Use the ExceptionClear method to clear this exception and throw an IllegalArgumentException exception instead.

A suspend exception raised through JNI (for example, by calling ThrowNew) does not immediately break the execution of the local method. This is different from the behavior of exceptions in the Java programming language. When an exception is thrown using the Java programming language, the Java virtual machine automatically transfers control to the nearest try/catch block that matches the exception type. The Java virtual machine then clears the pending exception and performs exception handling. In contrast, after an exception occurs, the JNI programmer must perform flow control explicitly.

6.1.2 A useful helper function

To throw an exception, first look up the exception’s class and call the ThrowNew method. To simplify this task, we can write a useful function that throws a named exception:

void JNU_ThrowByName(JNIEnv *env, const char *name, const char *msg) {
    jclass cls = (*env)->FindClass(env, name);
    /* if cls is NULL, an exception has already been thrown */
    if(cls ! =NULL) {
        (*env)->ThrowNew(env, cls, msg);
    }
    /* free the local ref */
    (*env)->DeleteLocalRef(env, cls);
}
Copy the code

In this book, JNU stands for JNI Utilities. JNU_ThrowByName first finds the exception class using the FindClass method. If the FindClass call fails (returning NULL), the virtual machine must throw an exception (such as NoClassDefFoundError). JNU_ThrowByName will not attempt to throw another exception this time. If FindClass returns successfully, we throw a named exception by calling ThrowNew. When the JNU_ThrowByName call returns, it is guaranteed to have a pending exception, although the pending exception is not necessarily specified by the name argument. In this method, we make sure that local references to exception classes are removed. If FindClass fails and returns NULL, passing NULL to DeleteLocalRef will be an empty operation, which would be an appropriate operation.

6.2 Proper exception handling

JNI programmers must encounter all possible exceptions and write code to check and handle them. Proper exception handling can sometimes be tedious, but it is necessary to improve the robustness of your program.

6.2.1 Exception Check

There are two ways to check if an error has occurred.

(1) Most JNI methods use an obvious return value (such as NULL) to indicate that an error has been generated. Returning an error value also means that a pending exception has been raised in the current thread. (Encoding error cases in return values is a common use in C)

The following example shows that GetFieldID returns NULL to check for errors. The example consists of two parts: the class Window defines instance fields (handle, Length, and Width) and has a local method to cache the field IDS of those fields. Although these fields are indeed already in the Window class, we still need to check for possible error values returned by GetFieldID, because the virtual machine may not be able to allocate enough content to hold the field ID.

/* a class in the Java programming language */
public class Window {
    long handle;
    int length;
    int width;
    static native void initIDs(a);
    static{ initIDs(); }}/* C code that implements Window.initIDs */
jfieldID FID_Window_handle;
jfieldID FID_Window_length;
jfieldID FID_Window_width;
JNIEXPORT void JNICALL Java_Window_initIDs(JNIEnv *env, jclass classWindow) {
    FID_Window_handle =(*env)->GetFieldID(env, classWindow, "handle"."J");
    if (FID_Window_handle == NULL) { /* important check. */
        return; /* error occurred. */
    }
    FID_Window_length =(*env)->GetFieldID(env, classWindow, "length"."I");
    if (FID_Window_length == NULL) { /* important check. */
        return; /* error occurred. */
    }
    FID_Window_width = (*env)->GetFieldID(env, classWindow, "width"."I");
    /* no checks necessary; we are about to return anyway */
}
Copy the code

(2) When using a JNI method, its return value cannot flag the occurrence of an error, the native code must rely on raising an exception for error checking. In the current thread, the JNI function used to check for pending exceptions is ExceptionOccurred. (ExceptionCheck was added in version 1.2 of the Java 2 JDK.) For example, the JNI CallIntMethod cannot encode an error condition as a return value. Typical error condition return values such as -1 and NULL do not work well because these are reasonable return values when they call the method. Consider having a Fraction class whose floor method returns the integer part of the Fraction value and other native code that calls this function.

public class Fraction {
    // details such as constructors omitted
    int over, under;
    public int floor(a) {
        return Math.floor((double)over/under); }}/* Native code that calls Fraction.floor. Assume method ID
MID_Fraction_floor has been initialized elsewhere. */
void f(JNIEnv *env, jobject fraction) {
    jint floor = (*env)->CallIntMethod(env, fraction, MID_Fraction_floor);
    /* important: check if an exception was raised */
    if ((*env)->ExceptionCheck(env)) {
        return; }.../* use floor */
}
Copy the code

When the JNI function returns a different error code, the native code may still check for exceptions by displaying the calling class, such as ExceptionCheck. However, it is still efficient to check for different return values. If a JNI method returns its error value, calling the ExceptionCheck method in subsequent processing of the current thread guarantees that JNI_TRUE will be returned.

6.2.2 Exception Handling

Native code can handle pending exceptions in two ways:

  • The native code implementation can choose to return immediately and do exception handling at the method caller
  • It is important for the native code to clear ExceptionClear by calling ExceptionClear and then execute its own exceptionhandler before calling any subsequent JNI functions to check, handle, and clear pending exceptions. Calling most JNI methods with pending exceptions, exceptions that have not been explicitly cleared, can lead to unexpected results. You can safely call only a small number of JNI methods when there is a pending exception in the current thread, and section 11.8.2 gives a complete list of these JNI functions. In general, when there is a pending exception, you can call specialized JNI functions to handle the exception and release the various virtual machine resources exposed through JNI.

When exceptions occur, it is often necessary to release resources. In the following example, the local method first retrieves the contents of the string with a call to GetStringChars. ReleaseStringChars is called if an error occurs in subsequent processing:

JNIEXPORT void JNICALL
Java_pkg_Cls_f(JNIEnv *env, jclass cls, jstring jstr)
{
    const jchar *cstr = (*env)->GetStringChars(env, jstr);
    if (c_str == NULL) {
        return; }...if(...). {/* exception occurred */
        (*env)->ReleaseStringChars(env, jstr, cstr);
        return; }.../* normal return */
    (*env)->ReleaseStringChars(env, jstr, cstr);
}
Copy the code

ReleaseStringChars is first called when a pending thread appears. The local method is implemented to release the string resource and return it immediately after, without needing to clear the exception first.

6.2.3 Exceptions in useful auxiliary functions

Programmers writing useful helper functions should take special care to ensure that exceptions propagate into locally called methods. We highlight two points in particular:

Preferably, the helper function should provide a special return value indicating that an exception has occurred. This simplifies the caller’s task of checking for exceptions to handle. In addition, helper functions should follow the rules governing local applications when managing exception code. To illustrate, let’s introduce an auxiliary function that performs a callback based on the name and descriptor of the instance method:

jvalue JNU_CallMethodByName(JNIEnv *env, jboolean *hasException, jobject obj, const char *name, const char *descriptor, ...)
{
    va_list args;
    jclass clazz;
    jmethodID mid;
    jvalue result;
    if((*env)->EnsureLocalCapacity(env, 2) == JNI_OK) {
        clazz = (*env)->GetObjectClass(env, obj);
        mid = (*env)->GetMethodID(env, clazz, name, descriptor);
        if(mid) {
            const char *p = descriptor;
            /* skip over argument types to find out the return type */
            while(*p ! =') ') p++;
            /* skip ')' */
            p++;
      va_start(args, descriptor);
      switch (*p) {
            case 'V':
                (*env)->CallVoidMethodV(env, obj, mid, args);
                break;

            case '[':
            case 'L':
                result.l = (*env)->CallObjectMethodV(env, obj, mid, args);
                break;

            case 'Z':
                result.z = (*env)->CallBooleanMethodV(env, obj, mid, args);
                break;

            case 'B':
                result.b = (*env)->CallByteMethodV(env, obj, mid, args);
                break;

            case 'C':
                result.c = (*env)->CallCharMethodV(env, obj, mid, args);
                break;

            case 'S':
                result.s = (*env)->CallShortMethodV(env, obj, mid, args);
                break;

            case 'I':
                result.i = (*env)->CallIntMethodV(env, obj, mid, args);
                break;

            case 'J':
                result.j = (*env)->CallLongMethodV(env, obj, mid, args);
                break;

            case 'F':
                result.f = (*env)->CallFloatMethodV(env, obj, mid, args);
                break;

            case 'D':
                result.d = (*env)->CallDoubleMethodV(env, obj, mid, args);
                break;

            default:
                (*env)->FatalError(env, "illegal descriptor");
            }
            va_end(args);
        }
        (*env)->DeleteLocalRef(env, clazz);
    }
    if (hasException) {
        *hasException = (*env)->ExceptionCheck(env);
    }
    return result
}
Copy the code

JNU_CallMethodByName has a pointer to JBoolean, among other parameters. Jboolean will be set to JNI_FALSE if all is well, and jBOOLEAN will be set to JNI_TRUE if an exception occurs at any time during the execution of this function. This will give the caller of JNU_CallMethoByName an obvious way to check if an exception has occurred.

JNU_CallMethodByName first ensures that he can create two local references: one for the class reference and one for the result returned by the method call. Next, it gets the class reference from the object and looks up the method ID. Depending on the type of return value, the Switch statement is scheduled to invoke the function to the corresponding JNI method. After the callback returns, if hasException is not NULL, we call ExceptionCheck to check for pending exceptions.

The ExceptionCheck method was added in the Java 2 SDK 1.2. It is similar to the ExceptionOccurred function. The difference is that ExceptionCheck does not return a reference to the exception object, but returns JNI_TRUE when there is a pending exception and JNI_FALSE when there is no pending exception. ExceptionCheck simplifies the management of local references when the local code only needs to know if an exception has occurred but does not need to obtain a reference to the exception object. The previous code will be rewritten using JDK 1.1 as follows:

if(hasException) { jthrowable exc = (*env)->ExceptionOccurred(env); *hasException = exc ! =NULL;
    (*env)->DeleteLocalRef(env, exc);
}
Copy the code

Additional DeleteLocalRef calls are required to remove local references to the exception object.

Use JNU_CallMethodByName method, we can rewrite InstanceMethodCall in section 4.2. The nativeMethod implementation:

JNIEXPORT void JNICALL
Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobject obj) {
    printf("In C\n");
    JNU_CallMethodByName(env, NULL, obj, "callback"."()V");
}
Copy the code

In the JNU_CallMethodByName call we don’t need to check for exceptions because the native code will return immediately.

Chapter 7: Calling interfaces

This chapter explains how to embed a Java virtual machine in your native code. Java Virtual Machine implementations are transported as a local library that local applications can connect to and use the call interface to load the Java virtual machine. Indeed, the standard launcher instruction in the JDK or Java 2 SDK version is nothing more than a simple C program linked to a Java virtual machine. The initiator parses command-line arguments, loads the virtual machine, and runs Java programs through invocation excuses.

7.1 Creating a Java VM

To illustrate the invocation excuse, let’s first look at a C program that loads a Java virtual machine and calls the Prog. Main method as defined below

public class Prog {
    public static void main(String[] args) {
        System.out.println("Hello World " + args[0]); }}Copy the code

The next C program, invoke.c, loads a Java virtual machine and calls the Prog. Main method

#include <jni.h>
#define PATH_SEPARATOR '; ' /* define it to be ':' on Solaris */
#define USER_CLASSPATH "." /* where Prog.class is */
main() {
    JNIEnv *env;
    JavaVM *jvm;
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jclass stringClass;
    jobjectArray args;
#ifdef JNI_VERSION_1_2
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString = "-Djava.class.path=" USER_CLASSPATH;
    vm_args.version = 0x00010002;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = JNI_TRUE;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
    JDK1_1InitArgs vm_args;
    char classpath[1024];
    vm_args.version = 0x00010001;
    JNI_GetDefaultJavaVMInitArgs(&vm_args);
    /* Append USER_CLASSPATH to the default system class path */
    sprintf(classpath, "%s%c%s", vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */

    if (res < 0) {
        fprintf(stderr."Can't create Java VM\n");
        exit(1);
    }

    cls = (*env)->FindClass(env, "Prog");
    if (cls == NULL) {
        goto destroy;
    }

    mid = (*env)->GetStaticMethodID(env, cls, "main"."([Ljava/lang/String;)V");
    if (mid == NULL) {
        goto destroy;
    }

    jstr = (*env)->NewStringUTF(env, " from C!");
    if (jstr == NULL) {
        goto destroy;
    }

    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
    if (args == NULL) {
        goto destroy;
    }

    (*env)->CallStaticVoidMethod(env, cls, mid, args);

destroy:
    if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);
    }
    (*jvm)->DestroyJavaVM(jvm);
Copy the code

The above code conditionally compiles the initialization structure JDK1_1InitArgs for the Java virtual machine implementation specific to JDK 1.1. Java 2 SDK version 1.2 still supports JDK1_1InitArgs, although it introduces an initialization structure called JavaVMInitArgs. The constant JAVA_VERSION_1_2 was defined in Java 2 SDK version 1.2, but not in JDK 1.1.

When it is for version 1.1, the C code gets the default virtual machine Settings by calling JNI_GetDefaultJavaVMInitArgs. JNI_GetDefaultJavaVMInitArgs returns things like heap size, stack size, and default classpath value in the vm_args argument. Then we append the Prog. Class directory to vm_args.classpath.

When it is for version 1.2, the C code creates a JavaVMInitArgs structure. The virtual machine initialization structure is stored in the JavaVMOption array, and you can set the normal options corresponding to the Java command line options (such as -djava.class.path =.). And the virtual machine implements specific options (such as -XMx64m). Setting the ignoreUnrecognized field to JNI_TRUE indicates that the VM ignores unrecognized vM-specific options.

After the virtual machine initialization structure is set up, the C program calls JNI_CreateJavaVM to load and initialize the Java virtual machine. JNI_CreateJavaVM will fill two return values:

  • JVM, an interface pointer to the newly created Java VIRTUAL machine
  • Env, a pointer to the current thread’s JNIEnv interface. The local method will use the env interface pointer to call JNI methods. When JNI_CreateJavaVM returns successfully, the current local thread has booted itself into the Java VIRTUAL machine. At this point, it acts like a native method, so, among other things, it can issue a JNI call to invoke the Prog. Main method.

Eventually, the program calls DestroyJavaVM to uninstall the Java virtual machine. (Unfortunately, you can’t uninstall the Java virtual machine implementation in JDK 1.1 and Java 2 SDK 1.2, where DestroyJavaVM always returns an error code.) Run the above program and you get:

Hello World from C!
Copy the code

7.2 Connecting a Local Application to a Java VM

Invoking the interface requires you to connect your program, such as invoke.c, to a Java virtual machine implementation. How you connect to the Java virtual machine depends on whether the local reference program is deployed only in a particular Java virtual machine implementation or is designed to work with various virtual machine implementations from different vendors.

7.2.1 Connecting to a Known Java VM

You may have decided that your application will only be deployed on a particular Java virtual machine implementation. In this case, you can connect the local application to the local library that implements the virtual machine. For example, in Solaris, JDK 1.1, you can compile and link invoke.c using the following command:

cc -I<jni.h dir> -L<libjava.so dir> -lthread -ljava invoke.c
Copy the code

The -lThread option indicates that we are using the Java virtual machine with local thread support (Section 8.1.5). The -ljava option indicates that libjava.so is the Solaris shared library that implements the Java virtual machine.

In Win32 system using Microsoft Visual C++ compiler, compile and link the same program command behavior

cl -I<jni.h dir> -MD invoke.c -link <javai.lib dir>\javai.lib
Copy the code

Of course you’ll need to provide the correct include and library directory that corresponds to the JDK installation on your machine. The -md option ensures that your native application is linked to the Win32 multithreaded C library, which is the same native C library used by Java virtual machine implementations in JDK 1.1 and Java 2 JDK 1.2. The CL command refers to the javai.lib file in JDK version 1.1 shipped with Win32 for link information about the call interface function (JNI_CreateJavaVM) implemented in the virtual machine. The actual JDK 1.1 virtual machine implementation used at runtime is contained in a separate dynamically linked library called javai.dll. Instead, the same Solaris dynamic library (.so files) is used both at link time and at run time.

In Java 2 JDK version 1.2, the names of the virtual libraries have been changed to libjvm.so on Solaris, and jvm.lib and jVM.dll on Win32. Often different vendors may name virtual machine implementations differently.

Once compiled and linked, you can run the generated executable from the command line. You may receive an error that the system could not find a shared or dynamically linked library. On Solaris systems, if an error message says the system cannot find the dynamic library libjava.so (libjvm.so on Java 2 JDK 1.2), you need to add the directory containing the virtual library to the LD_LIBRARY_PATH variable. On Win32 systems, the error message might indicate that it could not find the dynamic link library javai.dll (or jVM.dll on Java 2 JDK version 1.2). If this is the case, add the directory containing the DLL to the PATH environment variable.

7.2.2 Connecting to an Unknown Java VM

If the application is intended to use a virtual machine implementation from a different vendor, there is no way to link a local application to a specific virtual machine implementation library. Since JNI does not specify the name of the local library that implements the Java virtual machine, you should be prepared to use a different Java Virtual machine implementation. For example, on Win32, the virtual machine was released as javai.dll in JDK version 1.1 and as jVM.dll in Java 2 SDK version 1.2.

The solution is to use runtime dynamic linking to load specific virtual libraries required by the application. The name of the virtual library can be easily configured in an application-specific manner, such as the following Win32 code to find the JNI_CreateJavaVM function entry point for the path to the virtual library:

/* Win32 version */
void *JNU_FindCreateJavaVM(char *vmlibpath) {
    HINSTANCE hVM = LoadLibrary(vmlibpath);
    if (hVM == NULL) {
        return NULL;
    }
    return GetProcAddress(hVM, "JNI_CreateJavaVM");
}
Copy the code

LoadLibrary and GetProcAddreee are API functions for dynamic linking on Win32 systems. Although LoadLibrary can accept the name (for example, “JVM”) or path (for example, “C:\jdk1.2\jre\bin\classic\ Jvm.dll”) of the local library that implements the Java virtual machine, Sending the absolute strength of the local library to JNU_FindCreateJavaVM would be the best option. Relying on LoadLibrary to search for jVM.dll makes it easy for your application to make configuration changes, such as the addition of PATH environment variables.

The Solaris version is similar:

/* Solaris version */
void *JNU_FindCreateJavaVM(char *vmlibpath) {
    void *libVM = dlopen(vmlibpath, RTLD_LAZY);
    if (libVM == NULL) {
        return NULL;
    }
    return dlsym(libVM, "JNI_CreateJavaVM");
}
Copy the code

The Dlopen and DLSYm functions support dynamic linking of shared libraries on Solaris systems.

7.3 Attaching a Local Thread

Suppose you have a multithreaded application, such as a Web server written in C. As HTTP requests arrive, the server creates local threads to process the HTTP requests simultaneously. We want to embed a Java virtual machine in this server so that multiple threads can perform operations in the Java Virtual machine at the same time, as shown in Figure 7.1:

Local methods generated by the server may have a shorter life cycle than Java virtual machines. Therefore, we need a way to attach a local thread to an already running Java virtual machine, perform JNI calls in the attached local thread, and then disconnect the local thread from the Java Virtual machine from the other connected threads. The following example, attach.c, shows how to attach a local thread to a virtual machine using the call interface, written using the Win32 thread API. You can write similar versions for Solaris and other operating systems:

/* Note: This program only works on Win32 */
#include <windows.h>
#include <jni.h>
JavaVM *jvm; /* The virtual machine instance */

#define PATH_SEPARATOR '; '
#define USER_CLASSPATH "." /* where Prog.class is */

void thread_fun(void *arg) {
    jint res;
    jclass cls;
    jmethodID mid;
    jstring jstr;
    jclass stringClass;
    jobjectArray args;
    JNIEnv *env;
    char buf[100];
    int threadNum = (int)arg;

    /* Pass NULL as the third argument */
#ifdef JNI_VERSION_1_2
    res = (*jvm)->AttachCurrentThread(jvm, (void**)&env, NULL);
#else
    res = (*jvm)->AttachCurrentThread(jvm, &env, NULL);
#endif
    if (res < 0) {
        fprintf(stderr."Attach failed\n");
        return;
    }

    cls = (*env)->FindClass(env, "Prog");
    if (cls == NULL) {
        goto detach;
    }

    mid = (*env)->GetStaticMethodID(env, cls, "main"."([Ljava/lang/String;)V");
    if (mid == NULL) {
        goto detach;
    }

    sprintf(buf, " from Thread %d", threadNum);
    jstr = (*env)->NewStringUTF(env, buf);
    if (jstr == NULL) {
        goto detach;
    }

    stringClass = (*env)->FindClass(env, "java/lang/String");
    args = (*env)->NewObjectArray(env, 1, stringClass, jstr);
    if (args == NULL) {
        goto detach;
    }
    (*env)->CallStaticVoidMethod(env, cls, mid, args);

detach:
    if ((*env)->ExceptionOccurred(env)) {
        (*env)->ExceptionDescribe(env);
    }
    (*jvm)->DetachCurrentThread(jvm);
}

main() {
    JNIEnv *env;
    int i;
    jint res;

#ifdef JNI_VERSION_1_2
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];
    options[0].optionString = "-Djava.class.path=" USER_CLASSPATH;
    vm_args.version = 0x00010002;
    vm_args.options = options;
    vm_args.nOptions = 1;
    vm_args.ignoreUnrecognized = TRUE; /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
    JDK1_1InitArgs vm_args;
    char classpath[1024];
    vm_args.version = 0x00010001;
    JNI_GetDefaultJavaVMInitArgs(&vm_args);
    /* Append USER_CLASSPATH to the default system class path */
    sprintf(classpath, "%s%c%s", vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
    vm_args.classpath = classpath;
    /* Create the Java VM */
    res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */

    if (res < 0) {
        fprintf(stderr."Can't create Java VM\n");
        exit(1);
    }
    for (i = 0; i < 5; i++)
        /* We pass the thread number to every thread */
        _beginthread(thread_fun, 0, (void *)i);
        Sleep(1000); /* wait for threads to start */
        (*jvm)->DestroyJavaVM(jvm);
}
Copy the code

Attach. C is a variation of invoke.c. Instead of calling Prog. Main in the main thread, the native code starts five threads. Once it starts the threads, it waits for them to start and then calls DestroyJavaVM. Each generated thread links itself to the Java virtual machine, calls the prog. main method, and finally detaches it from the Java Virtual machine before it terminates. DestroyJavaVM will return after all five threads have terminated. Let’s ignore the return value of DestroyJavaVM for now, because this method is not fully implemented in JDK 1.1 and Java 2 JDK 1.2.

JNI_AttachCurrentThread takes NULL as its third argument. The JNI_ThreadAttachArgs structure was introduced in version 1.2 of the Java 2 JDK, which allows you to specify other parameters, such as the thread group to which you want to attach. The details of the JNI_ThreadAttachArgs structure are described as part of the JNI_AttachCurrentThread specification in Section 13.2.

When the program executes the DetachCurrentThread function, it frees all local references belonging to the current thread. Running the program produces the following output:

Hello World from thread 1 Hello World from thread 0 Hello World from thread 4 Hello World from thread 2 Hello World from  thread 3Copy the code

The exact order of outputs may vary depending on random factors in thread scheduling.

Chapter 8 Additional JNI features

We’ve already discussed the JNI features for writing native code and embedding a Java virtual machine implementation in native programs. In this chapter we introduce the remaining JNI features.

8.1 JNI and Threads

The Java virtual machine supports multiple threads of control executing simultaneously in the same address space. This concurrency introduces a level of complexity that is not present in a single-threaded environment. Multiple threads can simultaneously access the same object, the same file descriptor (the same shared resource for short).

To take full advantage of this section, you should be familiar with the concepts of multithreaded programming. You should know how to write Java applications that use multiple threads and synchronously access shared resources. A good reference book for multithreaded programming in the Java programming language is Concurrent Programming: Design Principles and Patterns by Doug Lea (Addison-Wesley, 1997).

8.1.1 constraints

When writing local methods to run in a multithreaded environment, you must keep in mind some constraints. By understanding and using these constraints, your local methods will execute safely no matter how many threads execute a given local method at the same time. Such as:

  • A JNIEnv pointer is only valid in the thread to which it is associated. You cannot pass this pointer from one thread to another, or cache and use it in multiple threads. The Java virtual machine passes the same JNIEnv pointer to local methods in successive calls from the same thread, but different JNIEnv Pointers are passed when local methods are called from different threads. The common mistake of caching a JNIEnv pointer in one thread and using it in another thread should be avoided.
  • A local reference is only valid in the thread that created it. You cannot pass local references from one thread to another. You should always convert a local reference to a global reference whenever there is a possibility that multiple threads might use the same reference. 8.1.2 Monitor Enter and exit monitor is the original synchronization mechanism on the Java platform. Each object can be dynamically associated with the monitor. JNI allows you to use these monitors for synchronization, which is equivalent to synchronization blocks in the Java programming language:
synchronized (obj) {
    ...     // synchronized block
}
Copy the code

The Java virtual machine ensures that the thread retrieves the monitor associated with the object OBj before executing any statement in the block. This ensures that at most one thread is holding the monitor and running within a synchronized block at any given time. While waiting for another thread to exit the monitor, that thread blocks.

Native code can use JNI functions to perform equivalent synchronization on JNI references. You can use the MonitorEnter method to enter the monitor and the MonitorExit method to exit it.

if((*env)->MonitorEnter(env, obj) ! = JNI_OK) { .../* error handling */}.../* synchronized block */
if((*env)->MonitorExit(env, obj) ! = JNI_OK) { .../* error handling */
};
Copy the code

To execute the code above, a thread must first go to the monitor associated with OBJ before executing any code within the synchronized block. The MonitorEnter operation takes a Jobject as an argument and blocks if another thread has entered the monitor associated with jobject. When the current thread didn’t also red optimal monitor call MonitorExit leads to errors and resulting in abnormal cause IllegalMonitorStateException. The code above contains a bunch of matching MonitorEnter and MonitorExit calls, but we still need to check for possible errors. Manipulating the monitor may fail if the underlying thread implementation is unable to allocate the resources needed to perform the monitor operation.

MonitorEnter and MonitorExit work on types jClass, JString, and JArray, which are special Jobject references.

Remember to match MonitorEnter calls with the appropriate number of MonitorExit calls, especially in code that handles errors and exceptions:

if ((*env)->MonitorEnter(env, obj) != JNI_OK) ...;
...
if ((*env)->ExceptionOccurred(env)) {
    ... /* exception handling */
    /* remember to call MonitorExit here */
    if((*env)->MonitorExit(env, obj) ! = JNI_OK) ... ; }.../* Normal execution path. if ((*env)->MonitorExit(env, obj) ! = JNI_OK) ... ;Copy the code

Failure to call MonitorExit may result in a deadlock. By comparing your C code snippet above with the snippet you wrote at the beginning of this section, you can see that programming in the Java programming language is much easier than JNI. It is therefore best to use the Java programming language to represent the synchronization structure. For example, if a static local method needs to enter the monitor associated with its definition, you should define a static synchronized local method instead of performing jNI-level monitor synchronization in native code.

8.1.3 Monitoring wait and notification

The Java API contains several methods for thread synchronization. They are Object.wait, Object.notify, and Object.notifyAll. JNI does not provide similar methods for direct correspondence to these methods, because monitoring wait and notification operations is not as performance critical as monitoring entry and exit. Native code might use the JNI method invocation mechanism to call the corresponding method in the Java API:

/* precomputed method IDs */
static jmethodID MID_Object_wait;
static jmethodID MID_Object_notify;
static jmethodID MID_Object_notifyAll;
void
JNU_MonitorWait(JNIEnv *env, jobject object, jlong timeout) {
    (*env)->CallVoidMethod(env, object, MID_Object_wait, timeout);
}

void
JNU_MonitorNotify(JNIEnv *env, jobject object) {
    (*env)->CallVoidMethod(env, object, MID_Object_notify);
}

void
JNU_MonitorNotifyAll(JNIEnv *env, jobject object) {
    (*env)->CallVoidMethod(env, object, MID_Object_notifyAll);
}
Copy the code

We assume that the method ids of Object.wait, object. notify, and Object.notifyAll have already been computed elsewhere and cached in global variables. Just like the Java programming language, the above monitor-related functions can only be invoked if you have a monitor associated with an Object parameter.

8.1.4 Getting a JNIEnv pointer in any Context

As we explained earlier, a JNIEnv pointer is only valid in the thread with which it is associated. For local methods, this is usually not a problem because they accept the JNIEnv pointer as the first argument from the virtual machine. Sometimes, however, you may not need native code called directly from the virtual machine to get a pointer to the JNIEnv interface belonging to the current thread. For example, one side of the native code that is part of the “callback” might be called by the operating system, in which case the JNIEnv pointer might not be used as a parameter. You can get the JNIEnv pointer for the current thread by calling AttachCurrentThread:

JavaVM *jvm; /* already set */
f() {
    JNIEnv *env;
    (*jvm)->AttachCurrentThread(jvm, (void **)&env, NULL); ./* use env */
}
Copy the code

When the current thread is attached to the VIRTUAL machine, AttachCurrentThread returns a pointer to the JNIEnv interface belonging to the current thread.

There are a number of ways to get JavaVM Pointers: Unlike the JNIEnv pointer, record the VIRTUAL machine by logging it when it is created, by querying the virtual machine that has been created using JNI_GetCreatedJavaVMs, by calling GetJavaVM in a regular method, or by defining the JNI_OnLoad handler, JavaVM Pointers remain valid across multiple processes and can therefore be cached in global variables.

The Java 2 SDK version 1.2 provides a new call interface method, GetEnv, so you can use it to check if the current thread is attached to a virtual machine, and if so, it returns a JNIEnv pointer belonging to the current thread.

8.1.5 Thread Model Matching

Assume that local code executes in multiple threads and accesses the same global reference. Should the native code use the JNI functions MonitorEnter and MonitorExit or the native thread synchronization primitives in the host environment (such as mutex_lock on Solaris)? Also, if your native code needs to create a new Thread, should you create a java.lang.Thread object and call Thread.start via JNI, or should you use a native Thread creation primitive in the host environment (such as thr_create on Solaris)?

The answer is that all of these approaches work if the Java virtual machine implementation supports a threading model that matches the one used by native code. The threading model indicates how the system implements basic threading operations, such as system scheduling, context switching, synchronization, and blocking. In the local threading model, the operating system manages all of these required threading operations. On the other hand, in a user threading model, application code implements threading operations. For example, the “Green Thread” model in the JDK shipped with Solaris and the Java 2 SDK version uses the ANSI C functions setjmp and Longjmp for context switching.

Many modern operating systems, such as Solaris and Win32, support the native threading model. Unfortunately, some operating systems still lack native threading support. Instead, there may be one or more user thread packages on these operating systems.

If you write your application strictly in the Java programming language, you don’t have to worry about the underlying threading model that the virtual machine implements. The Java platform can be ported to any host environment that supports the required threading primitives. Most local and user threading packages provide the threading primitives necessary to implement the Java virtual machine.

JNI programmers, on the other hand, must pay attention to the threading model. If Java virtual machine implementations and native code have different concepts of threading and synchronization, applications using native code may not function properly. For example, a local method might block when performing synchronous operations in its own thread model, but a Java virtual machine running a different thread model might not be aware that the thread executing the local method is blocked. The application is deadlocked because other threads cannot be called. If the native code and the Java virtual machine implementation use the same threading model, their threading model matches. If the Java virtual machine implementation supports native threads, then native code is free to call thread-specific primitives in the host environment. If the Java virtual machine implementation is based on a user thread package, then native code should link to the same user thread package or not rely on multithreaded operations. The latter may be harder to achieve than you think: most C library calls (such as I/O and memory allocation functions) perform low-level thread synchronization. Unless the native code does pure computation and library calls are not applicable, threading primitives may be used indirectly.

Most virtual machines are implemented as JNI native code that supports only a specific threading model. Virtual machine implementations that support native threads are the most flexible, so local threads, when available, should be preferred in host environments. Virtual machine implementations that rely on feature user thread packages can be severely limited by the type of native code they can manipulate.

Some virtual machine implementations may support many different threading models. The more flexible virtual machine implementation types even allow you to provide custom thread model implementations for internal virtual machine use, ensuring that the virtual machine implementation works with your native code. Before starting a project that requires native code, you should review the documentation that comes with the virtual machine implementation to understand the limitations of the threading model.

8.2 Write code for internationalization

Special care is needed when writing code that works well in more than one country. JNI gives programmers full access to the internationalization features of the Java platform. We use character substitution as an example, because in many locales, file names and messages can contain many non-ASCII characters.

The Java virtual machine uses Unicode format to represent strings. Although some native platforms (such as Windows NT) also support Unicode, locale-specific encodings are used to represent strings.

Do not use the GetStringUTFChars and GetStringUTFRegion functions to convert between Jstrings and locale-specific strings, unless UTF-8 happens to be the platform’s native encoding. Utf-8 strings are useful when representing names and descriptors (such as arguments to GetMethodID), which are passed to JNI functions, but are not applicable to locale-specific strings, such as file names.

8.2.1 Creating JStrings from local Strings

Convert a local String to a JString using the String (byte[] bytes) constructor. The next helper function creates a JString from a LOCALe-encoded C string

jstring JNU_NewStringNative(JNIEnv *env, const char *str) {
    jstring result;
    jbyteArray bytes = 0;
    int len;
    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
        return NULL; /* out of memory error */
    }
    len = strlen(str);
    bytes = (*env)->NewByteArray(env, len);
    if(bytes ! =NULL) {
        (*env)->SetByteArrayRegion(env, bytes, 0, len, (jbyte *)str);
        result = (*env)->NewObject(env, Class_java_lang_String, MID_String_init, bytes);
        (*env)->DeleteLocalRef(env, bytes);
        return result;
    } /* else fall through */
    return NULL;
}
Copy the code

This method creates a byte array, copies the local C String into the Byte array, and finally calls the constructor of String (byte[] bytes) to create the final JString object. Class_java_lang_String is a global reference to java.lang.String, and MID_String_init is the method ID of the String constructor. Since this is a helper function, we need to make sure we remove the local application Byte array that was created temporarily to hold the characters.

If you need to use this function in JDK version 1.1, remove the call to EnsureLocalCapacity.

C8.2.2 converts jStrings to local strings

Use the string.getBytes method to convert a JString String to the appropriate local encoding. The following helper functions convert a JString string to a locale-specific local string:

char *JNU_GetStringNativeChars(JNIEnv *env, jstring jstr)
{
    jbyteArray bytes = 0;
    jthrowable exc;
    char *result = 0;
    if ((*env)->EnsureLocalCapacity(env, 2) < 0) {
        return 0; /* out of memory error */
    }
    bytes = (*env)->CallObjectMethod(env, jstr, MID_String_getBytes);
    exc = (*env)->ExceptionOccurred(env);
    if(! exc) { jint len = (*env)->GetArrayLength(env, bytes); result = (char *)malloc(len + 1);
        if (result == 0) {
            JNU_ThrowByName(env, "java/lang/OutOfMemoryError".0);
            (*env)->DeleteLocalRef(env, bytes);
            return 0;
        }
        (*env)->GetByteArrayRegion(env, bytes, 0, len, (jbyte *)result);
        result[len] = 0; /* NULL-terminate */
    } else {
        (*env)->DeleteLocalRef(env, exc);
    }
    (*env)->DeleteLocalRef(env, bytes);
    return result;
}
Copy the code

This method passes a java.lang.String reference to the String.getBytes method and copies the elements of the Byte array into a newly allocated C array. MID_String_getBytes is the precomputed method ID of String.getBytes. Since this is a helper function, we need to ensure that the local reference to the Byte array is removed and handle the exception object. Keep in mind that deleting a JNI reference to an exception object does not clear pending exceptions.

Again, if you need to use this function in JDK version 1.1, you need to remove the call to EnsureLocalCapacity.

8.3 Registering a Local User

Before an application executes a local method, it performs two steps to load the local library containing the local code implementation and then link to the local method implementation.

  • System.loadlibrary locates and loads named local libraries. For example, in Win32 system. loadLibrary(” foo “) would be foo. DLL loaded.
  • The virtual machine locates the local method implementation in the loaded local library. For example, a foo. g method call needs to locate and link to a native method Java_Foo_g that might exist in the foo.dll library. This section describes another way to do this second step. JNI programmers can use a class reference, method name, and method descriptor to register methods of method Pointers to manually connect to the local library, rather than relying on the virtual machine to look up local methods in the loaded local library:
JNINativeMethod nm;
nm.name = "g";
/* method descriptor assigned to signature field */
nm.signature = "()V";
nm.fnPtr = g_impl;
(*env)->RegisterNatives(env, cls, &nm, 1);
Copy the code

The above code registers a local method g_impl as a local implementation of the foo. g method:

void JNICALL g_impl(JNIEnv *env, jobject self);
Copy the code

The local method g_impl does not need to follow JNI naming conventions because it involves only function pointer calls and does not need to be exported from the library (hence the need not to declare methods using JNIEXPORT). However, the local g_impl method still needs to follow the JNICALL invocation rules.

The RegisterNatives method has many uses:

  • It is more convenient and efficient to register a large number of local method implementations, rather than having the VIRTUAL machine lazily connect to the entry points of these methods.
  • You can call the RegisterNatives method several times in one method, allowing the local method implementation to be updated at runtime.
  • RegisterNatives is useful when a local application needs to embed a virtual machine implementation and needs to link to local methods defined in the local application. The virtual machine cannot automatically look up this local method reference because it can only look up in the local library and not in the application.

8.4 Loading and unloading handlers

The load and unload handlers allow local libraries to export two methods: one called when system.loadLibrary loads the local library and the other called when the virtual machine unloads the local library. This feature was added to the Java 2 SDK version 1.2.

8.4.1 JNI_OnLoad handler

When System. Loadlibrary loads a local library, the virtual machine looks for the following exported program entry in the local library:

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved);
Copy the code

Within a JNI_OnLoad implementation, you can call any JNI method. A typical use of a JNI_OnLoad handler is to buffer JavaVM Pointers, class references, or method and field ids, as shown in the example below:

JavaVM *cached_jvm;
jclass Class_C;
jmethodID MID_C_g;
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
    JNIEnv *env;
    jclass cls;
    cached_jvm = jvm; /* cache the JavaVM pointer */
    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
        return JNI_ERR; /* JNI version not supported */
    }
    cls = (*env)->FindClass(env, "C");
    if (cls == NULL) {
        return JNI_ERR;
    }
    /* Use weak global ref to allow C class to be unloaded */
    Class_C = (*env)->NewWeakGlobalRef(env, cls);
    if (Class_C == NULL) {
        return JNI_ERR;
    }
    /* Compute and cache the method ID */
    MID_C_g = (*env)->GetMethodID(env, cls, "g"."()V");
    if (MID_C_g == NULL) {
        return JNI_ERR;
    }
    return JNI_VERSION_1_2;
}
Copy the code

The JNI_OnLoad method first caches the JavaVM pointer in the global variable cached_JVM. Then get the JNIEnv pointer by calling GetEnvironment. Finally, load the C class, or class reference, and calculate the method ID of C.g. The JNI_OnLoad method returns JNI_ERR(Section 12.4) as an error indication, otherwise returns the version of JNIEnv required by the local library, JNI_VERSION_1_2.

We will explain in the next section why we cache class C in a weak global reference rather than a global reference.

Given a cached JavaVM interface pointer, it is easy to implement a helper function that allows native code to get a pointer to the current thread’s JNIEnv interface.

JNIEnv *JNU_GetEnv(a) {
    JNIEnv *env;
    (*cached_jvm)->GetEnv(cached_jvm, (void **)&env, JNI_VERSION_1_2);
    return env;
}
Copy the code

8.4.2 JNI_OnUnload handler

Intuitively, when a virtual machine unloads a local library, it calls the JNI_OnUnload handler. However, this is not precise enough. When can a virtual machine confirm that it can uninstall a local library? Which thread executes the JNI_OnUnload handler?

The rules for uninstalling local libraries are as follows:

  • The virtual machine associates each local library with a classloader L of class C that makes the system.loadLibrary call
  • When the virtual machine confirms that there are no more living objects for the L loader, it calls the JNI_OnUnload handler and unloads the local libraries. Because the classloader refers to all the classes it defines, this means that C can also be unloaded.
  • JNI_OnUnload handler in a finalizer, there can be a Java lang. RunFinalization synchronous calls, can also be asynchronous calls by the virtual machine. Here is a definition of the JNI_OnUnload handler that clarifies the resources requested by the JNI_OnLoad handler in the previous section
JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *jvm, void *reserved) {
    JNIEnv *env;
    if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_2)) {
        return;
    }
    (*env)->DeleteWeakGlobalRef(env, Class_C);
    return;
}
Copy the code

The JNI_OnUnload method removes global weak references to C classes created in the JNI_OnLoad handler. We do not need to remove the method ID of MID_C_g, because the virtual machine automatically reclaims the resources used by the method ID of class C when uninstalling the class C defined by it.

Now we are ready to explain why we cache class C in a weak global reference, but not in a global reference. A global reference keeps C alive, which also keeps C class loaders alive. A given local library is associated with the C class loader L, so the local library will not be unloaded and JNI_OnUnload will not be called.

The JNI_UnLoad handler runs in a finalizer. Instead, the JNI_OnLoad handler runs in the thread that initiates the System.loadLibrary call. Because JNI_OnUnload runs in an unknown thread context, you should avoid complex synchronization and locking operations in JNI_OnUnload to avoid possible deadlocks. JNI_OnUnload usually performs simple tasks, such as releasing resources requested by a local library. The JNI_OnUnload handler runs when the library’s class loader is loaded and the classes defined by the class loader are no longer alive. The JNI_OnUnload handler will not use these classes in any way. In the JNI_OnUnload definition above, you cannot do anything that assumes Class_C still references a valid class. The DeleteWeakGlobalRef call in the example frees memory for the weak global reference itself, but does not operate on class C in any way.

In summary, you should be very informative when calling the JNI_OnUnload handler to avoid deadlocks caused by complex locking operations. Remember, when the JNI_OnUnload handler is called, the class has already been unloaded.

8.5 Reflection Support

Reflection usually refers to run-time manipulation of language-level structures. For example, reflection allows you to discover the name of any class object and the fields and methods defined in the class’s columns at run time. The Java programming language supports reflection through the java.lang. Reflect package and some methods in the java.lang.Object and java.lang.Class classes. Although you can often call the appropriate Java API for reflection, JNI provides the following methods to make the frequent reflection of native code more efficient and convenient:

  • GetSuperClass returns a superclass referenced by a given class.
  • IsAssignableFrom is used to check whether an instance of a class can be used when an instance of another class produces the desired effect.
  • GetObjectClass returns the class referenced by the given object
  • IsInstanceOf checks if a Jobject reference is an instance of a given class.
  • FromReflectedField and ToReflectedField allow native code to convert between the Field ID and the jva.lang.reflect.Field object. This is a new addition to the Java 2 SDK version 1.2.
  • FromReflectedMethod and ToReflectedMethod allow native code in the Method ID, Java, lang. Reflect. The Method and Java objects. Lang. Reflect. The transformation between object Constructor. This is a new addition to the Java 2 SDK version 1.2.

8.6 JNI programming using C++

JNI provides a slightly simpler interface for C++ programmers. The jni.h file contains a set of definitions for C++ programmers to write, such as:

jclass cls = env->FindClass("java/lang/String");
Copy the code

In C:

jclass cls = (*env)->FindClass(env, "java/lang/String");
Copy the code

The additional levels of indirection on env and the env argument to FindClass are hidden from the programmer. The C++ compiler inlines C++ member functions to the corresponding C objects, resulting in exactly the same code. There is no inherent performance difference in using JNI in C and C++. In addition, the jni.h file defines a set of virtual C++ classes to enforce subtype relationships between different jobject subtypes:

// JNI reference types defined in C++
class _jobject {};
class _jclass : public _jobject {};
class _jstring : public_jobject {}; .typedef _jobject* jobject;
typedef _jclass* jclass;
typedef_jstring* jstring; .Copy the code

The C++ compiler can check for a pass at compile time, for example by passing a jobject to GetMethodID:

// ERROR: pass jobject as a jclass:jobject obj = env->NewObject(...) ; jmethodID mid = env->GetMethodID(obj,"foo"."()V");
Copy the code

Because GetMethodID expects a jclass reference, the C++ compiler will give you the wrong information. In JNI’s C type definition, jClass and jobject are equivalent:

typedef jobject jclass;
Copy the code

Therefore, the C compiler cannot detect that you passed Jobject incorrectly instead of jClass.

&ems; Type hierarchies added to C++ sometimes require additional projections. In C, you can take a string from an array of strings and assign the result to a jString:

jstring jstr = (*env)->GetObjectArrayElement(env, arr, i);
Copy the code

But in C++, you need to insert a display transform:

jstring jstr = (jstring)env->GetObjectArrayElement(arr, i);
Copy the code