JNI Enhancements in JDK 1.2

(Beta 4 Draft, 4/20/98)

JNI will be enhanced in 1.2 to:

The changes are driven by licensee and user comments. Please continue to send your comments, suggestions, and concerns to jni@java.sun.com.

Changes since Beta 1 Draft, 9/20/97

New Constants

#define JNI_VERSION_1_1 0x00010001
#define JNI_VERSION_1_2 0x00010002

/* Error codes */
#define JNI_EDETACHED    (-2)              /* thread detached from the VM */
#define JNI_EVERSION     (-3)              /* JNI version error */
#define JNI_ENOMEM       (-4)              /* not enough memory */
#define JNI_EEXIST       (-5)              /* VM already created */
#define JNI_EINVAL       (-6)              /* invalid arguments */

Enhancements to the Existing API

New Functions

Class Operations

In the enhanced Java security model, non-system classes can load native code. The JNI function FindClass has been extended so that it finds classes loaded with a class loader.

jclass FindClass(JNIEnv *env, const char *name);

In JDK 1.1, FindClass searched only local classes in CLASSPATH. The resulting classes did not have a class loader.

The Java security model has been extended to allow non-system classes to load and call native methods. In JDK 1.2, FindClass locates the class loader associated with the current native method. If the native code belongs to a system class, no class loader will be involved. Otherwise, the proper class loader will be invoked to load and link the named class.

When FindClass is called through the Invocation Interface, there is no current native method or its associated class loader. In that case, the result of ClassLoader.getBaseClassLoader is used. This is the class loader the virtual machine creates for applications, and is able to locate classes listed in the java.class.path property.

Library and Version Management

In JDK 1.1, once a native library is loaded, it is visible from all class loaders. Therefore two classes in different class loaders may link with the same native method. This leads to two problems:

In JDK 1.2, each class loader manages its own set of native libraries. The same JNI native library cannot be loaded into more than one class loader. Doing so causes UnsatisfiedLinkError to be thrown. For example, System.loadLibrary throws an UnsatisfiedLinkError when used to load a native library into two class loaders. The benefits of the new approach are:

To facilitate versioning control and resource management, JNI libraries in 1.2 may optionally export the following two functions:

jint JNI_OnLoad(JavaVM *vm, void *reserved);

The VM calls JNI_OnLoad when the native library is loaded (for example, through System.loadLibrary). JNI_OnLoad must return the JNI version needed by the native library.

In order to use any of the new 1.2 JNI functions, a native library must export a JNI_OnLoad function that returns JNI_VERSION_1_2. If the native library does not export a JNI_OnLoad function, the VM assumes that the library only requires JNI version JNI_VERSION_1_1. If the VM does not recognize the version number returned by JNI_OnLoad, the native library cannot be loaded.

void JNI_OnUnload(JavaVM *vm, void *reserved);

The VM calls JNI_OnUnload when the class loader containing the native library is garbage collected. This function can be used to perform cleanup operations. Because this function is called in an unknown context (such as from a finalizer), the programmer should be conservative on using Java VM services, and refrain from arbitrary Java call-backs.

Note that JNI_OnLoad and JNI_OnUnload are two functions optionally supplied by JNI libraries, not exported from the VM.

Local Reference Management

Local references are valid for the duration of a native method call. They are freed automatically after the native method returns. Each local reference costs some amount of Java Virtual Machine resource. Programmers need to make sure that native methods do not excessively allocate local references. Although local references are automatically freed after the native method returns to Java, excessive allocation of local references may cause the VM to run out of memory during the execution of a native method.

JDK 1.1 provides a DeleteLocalRef function so that programmers can manually delete local references. For example, if native code iterates through a potentially large array of objects and uses one element in each iteration, it is a good practice to delete the local reference to the no-longer-used array element before a new local reference is created in the next iteration.

JDK 1.2 provides an additional set of functions for local reference lifetime management.

jint EnsureLocalCapacity(JNIEnv *env, jint capacity);

Ensures that at least a given number of local references can be created in the current thread. Returns 0 on success; otherwise returns a negative number and throws an OutOfMemoryError.

Before it enters a native method, the VM automatically ensures that at least 16 local references can be created.

For backward compatibility, the VM allocates local references beyond the ensured capacity. (As a debugging support, the VM may give the user warnings that too many local references are being created. In JDK 1.2, the programmer can supply the -verbose:jni command line option to turn on these messages.) The VM calls FatalError if no more local references can be created beyond the ensured capacity.

jint PushLocalFrame(JNIEnv *env, jint capacity);

Creates a new local reference frame, in which at least a given number of local references can be created. Returns 0 on success, a negative number and a pending OutOfMemoryError on failure.

Note that local references already created in previous local frames are still valid in the current local frame.

jobject PopLocalFrame(JNIEnv *env, jobject result);

Pops off the current local reference frame, frees all the local references, and returns a local reference in the previous local reference frame for the given result object.

Pass NULL as result if you do not need to return a reference to the previous frame.

jobject NewLocalRef(JNIEnv *env, jobject ref);

Creates a new local reference that refers to the same object as ref. The given ref may be a global or local reference. Returns NULL if ref refers to null.

Weak Global References

Weak global references are a special kind of global reference. Unlike normal global references, a weak global reference allows the underlying Java object to be garbage collected. Weak global references may be used in any situations where global or local references are used. When the garbage collector runs, it frees the underlying object if the object is only referred to by weak references. A weak global reference pointing to a freed object is functionally equivalent to NULL. Programmers can detect whether a weak global reference points to a freed object by using IsSameObject to compare the weak reference against NULL.

Weak global references in JNI are a simplified version of the Java Weak References, available as part of the core 1.2 API ( java.lang.Ref and its related classes).

jweak NewWeakGlobalRef(JNIEnv *env, jobject obj);

Creates a new weak global reference. Returns NULL if obj refers to null, or if the VM runs out of memory. If the VM runs out of memory, an OutOfMemoryError will be thrown.

void DeleteWeakGlobalRef(JNIEnv *env, jweak obj);

Delete the VM resources needed for the given weak global reference.

Array Operations

In JDK 1.1, programmers can use Get/ReleaseArrayElements functions to obtain a pointer to primitive array elements. If the VM supports pinning, the pointer to the original data is returned; otherwise, a copy is made.

New 1.2 functions allow native code to obtain a direct pointer to array elements even if the VM does not support pinning.

void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

The semantics of these two functions are very similar to the existing Get/ReleaseArrayElements functions. If possible, the VM returns a pointer to the primitive array; otherwise, a copy is made. However, there are significant restrictions on how these functions can be used.

After calling GetPrimitiveArrayCritical, the native code should not run for an extended period of time before it calls ReleasePrimitiveArrayCritical. We must treat the code inside this pair of functions as running in a "critical region." Inside a critical region, native code must not call other JNI functions, or any system call that may cause the current thread to block and wait for another Java thread. (For example, the current thread must not call read on a stream being written by another Java thread.)

These restrictions make it more likely that the native code will obtain an uncopied version of the array, even if the VM does not support pinning. For example, a VM may temporarily disable garbage collection when the native code is holding a pointer to an array obtained via GetPrimitiveArrayCritical.

Multiple pairs of GetPrimtiveArrayCritical and ReleasePrimitiveArrayCritical may be nested. For example:

  jint len = (*env)->GetArrayLength(env, arr1); 
  jbyte *a1 = (*env)->GetPrimitiveArrayCritical(env, arr1, 0);
  jbyte *a2 = (*env)->GetPrimitiveArrayCritical(env, arr2, 0);
  /* We need to check in case the VM tried to make a copy. */
  if (a1 == NULL || a2 == NULL) {
    ... /* out of memory exception thrown */
  }
  memcpy(a1, a2, len);
  (*env)->ReleasePrimitiveArrayCritical(env, arr2, a2, 0);
  (*env)->ReleasePrimitiveArrayCritical(env, arr1, a1, 0);

Note that GetPrimitiveArrayCritical might still make a copy of the array if the VM internally represents arrays in a different format. Therefore we need to check its return value against NULL for possible out of memory situations.

String Operations

In JDK 1.1, programmers can get primitive array elements in a user-supplied buffer. JDK 1.2 allows native code to obtain Unicode or UTF-8 characters in a user-supplied buffer.

void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize len, jchar *buf);

Copies len number of Unicode characters beginning at offset start to the given buffer buf.

Throws StringIndexOutOfBoundsException on index overflow.

void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);

Translates len number of Unicode characters beginning at offset start into UTF-8 format and place the result in the given buffer buf.

Throws StringIndexOutOfBoundsException on index overflow.

const jchar * GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy);
void ReleaseStringCritical(JNIEnv *env, jstring string, const jchar *carray);

The semantics of these two functions are similar to the existing Get/ReleaseStringChars functions. If possible, the VM returns a pointer to string elements; otherwise, a copy is made. However, there are significant restrictions on how these functions can be used. In a code segment enclosed by Get/ReleaseStringCritical calls, the native code must not issue arbitrary JNI calls, or cause the current thread to block.

The restrictions on Get/ReleaseStringCritical are similar to those on Get/ReleasePrimitiveArrayCritical.

Reflection Support

Programmers can use the JNI to call Java methods or access Java fields if they know the name and type of the methods or fields. The Java Core Reflection API allows programmers to introspect Java classes at runtime. JNI provides a set of conversion functions between field and method IDs used in the JNI to field and method objects used in the Java Core Reflection API.

jmethodID FromReflectedMethod(JNIEnv *env, jobject method);

Converts a java.lang.reflect.Method or java.lang.reflect.Constructor object to a method ID.

jfieldID FromReflectedField(JNIEnv *env, jobject field);

Converts a java.lang.reflect.Field to a field ID.

jobject ToReflectedMethod(JNIEnv *env, jclass cls,
   jmethodID methodID);

Converts a method ID derived from cls to a java.lang.reflect.Method or java.lang.reflect.Constructor object.

Throws OutOfMemoryError and returns 0 if fails.

jobject ToReflectedField(JNIEnv *env, jclass cls,
   jfieldID fieldID);

Converts a field ID derived from cls to a java.lang.reflect.Field object.

Throws OutOfMemoryError and returns 0 if fails.

Invocation API

jint JNI_CreateJavaVM(JavaVM **pvm, void **penv, void *args);

In JDK 1.1, the second argument to JNI_CreateJavaVM is always a pointer to JNIEnv *. The third argument is a pointer to a JDK 1.1 specific structure (JDK1_1InitArgs). The JDK1_1InitArgs structure is clearly not designed to be portable on all VMs.

In JDK 1.2, we introduce a standard VM initialization structure. Backward compatibility is preserved. If the VM initialization argument points to a JDK1_1InitArgs structure, JNI_CreateJavaVM still returns the 1.1 version of JNI interface pointer. The VM returns the 1.2 version of JNI interface pointer if the third argument points to a JavaVMInitArgs structure. Unlike JDK1_1InitArgs, which contains a fixed set of options, JavaVMInitArgs uses option strings to encode arbitrary VM start up options.

typedef struct JavaVMInitArgs {
    jint version;

    jint nOptions;
    JavaVMOption *options;
    jboolean ignoreUnrecognized;
} JavaVMInitArgs;
The version field must be set to JNI_VERSION_1_2. (In contrast, the version field in JDK1_1InitArgs must be set to JNI_VERSION_1_1.) The options field is an array of the following type:
typedef struct JavaVMOption {
    char *optionString;
    void *extraInfo;
} JavaVMOption;
The size of the array is denoted by the nOptions field in JavaVMInitArgs. If ignoreUnrecognized is JNI_TRUE, JNI_CreateJavaVM ignore all unrecognized option strings that begin with "-X" or "_". If ignoreUnrecognized is JNI_FALSE, JNI_CreateJavaVM returns JNI_ERR as soon as it encounters any unrecognized option strings. All Java VMs must recognize the following set of standard options:

optionString meaning
-D<name>=<value> Set a system property
-verbose[:class|gc|jni] Enable verbose output. The options can be followed by a comma-separated list of names indicating what kind of messages will be printed by the VM. For example, "-verbose:gc,class" instructs the VM to print GC and class loading related messages. Standard names include: gc, class, and jni. All nonstandard (VM-specific) names must begin with "X".
vfprintf extraInfo is a pointer to the vfprintf hook.
exit extraInfo is a pointer to the exit hook.
abort extraInfo is a pointer to the abort hook.

In addition, each VM implementation may support its own set of non-standard option strings. Non-standard option names must begin with "-X" or an underscore ("_"). For example, JDK 1.2 supports -Xms and -Xmx options to allow programmers specify the initial and maximum heap size. Options that begin with "-X" are accessible from the "java" command line.

Here is the example code that creates a Java VM in JDK 1.2:

JavaVMInitArgs vm_args;
JavaVMOption options[4];

options[0].optionString = "-Djava.compiler=NONE";           /* disable JIT */
options[1].optionString = "-Djava.class.path=c:\myclasses"; /* user classes */
options[2].optionString = "-Djava.library.path=c:\mylibs";  /* set native library path */
options[3].optionString = "-verbose:jni";                   /* print JNI-related messages */

vm_args.version = JNI_VERSION_1_2;
vm_args.options = options;
vm_args.nOptions = 4;
vm_args.ignoreUnrecognized = TRUE;

/* Note that in JDK 1.2, there is no longer any need to call 
 * JNI_GetDefaultJavaVMInitArgs. 
 */
res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);
if (res < 0) ...

JDK 1.2 still supports JDK1_1InitArgs in exactly the same way as JDK 1.1.

jint AttachCurrentThread(JavaVM *vm, void **penv, void *args);

In JDK 1.1, the second argument to AttachCurrentThread is always a pointer to JNIEnv. The third argument to AttachCurrentThread was reserved, and should be set to NULL.

In JDK 1.2, you pass NULL as the third argument for 1.1 behavior, or pass a pointer to the following structure to specify additional information:

typedef struct JavaVMAttachArgs {
    jint version;  /* must be JNI_VERSION_1_2 */
    char *name;    /* the name of the thread, or NULL */
    jobject group; /* global ref of a ThreadGroup object, or NULL */
} JavaVMAttachArgs;

jint DetachCurrentThread(JavaVM *vm);

In JDK 1.1, the main thread cannot be detached from the VM. It must call DestroyJavaVM to unload the entire VM.

In JDK 1.2, the main thread can be detached from the VM.

jint DestroyJavaVM(JavaVM *vm);

The support for DestroyJavaVM was not complete in 1.1. Only the main thread may call DestroyJavaVM. In JDK 1.2, any thread, whether attached or not, can call this function. If the current thread is attached, the VM waits until the current thread is the only user-level Java thread. If the current thread is not attached, the VM attaches the current thread and then waits until the current thread is the only user-level thread. JDK 1.2 still does not support VM unloading, however. DestroyJavaVM always returns an error code.

jint GetEnv(JavaVM *vm, void **env, jint version);

If the current thread is not attached to the VM, sets *env to NULL, and returns JNI_EDETACHED. If the specified version is not supported, sets *env to NULL, and returns JNI_EVERSION. Otherwise, sets *env to the appropriate interface, and returns JNI_OK.

Comments to: jni@java.sun.com
Last modified: Wed May 6 22:41:31 PDT 1998