Overview
The JNI is a native programming interface. It allows Java code that runs inside a Java Virtual Machine (VM) to interoperate with applications and libraries written in other programming languages, such as C, C++, and assembly.
The most important benefit of the JNI is that it imposes no restrictions on the implementation of the underlying Java VM. Therefore, Java VM vendors can add support for the JNI without affecting other parts of the VM. Programmers can write one version of a native application or library and expect it to work with all Java VMs supporting the JNI.
Native code accesses Java VM features by calling JNI functions. JNI functions are available through an interface pointer. An interface pointer is a pointer to a pointer. This pointer points to an array of pointers, each of which points to an interface function. Every interface function is at a predefined offset inside the array.
The JNI interface pointer is only valid in the current thread. A native method, therefore, must not pass the interface pointer from one thread to another. A VM implementing the JNI may allocate and store thread-local data in the area pointed to by the JNI interface pointer.
Interfaces
JNI contains a set of global functions, as well as two structs that contains function tables.
JNI_CreateJavaVM(JavaVM **p_vm, void **p_env, void *vm_args)
JavaVM
- represents the VM
- per process, shared by all the threads of the process
- operate on the VM, such as creation and destroying of the JVM.
- has the following operations
DestroyJavaVM()
AttachCurrentThread(void **penv, void *args)
DetachCurrentThread()
GetEnv(void **penv, jint version)
JNIEnv
- thread dependent
- contains some thread-local data and a function table
- operate on classes and methods by calling methods of the VM data structures.
Note that there are also some other global functions that are not included above, such as JNI_onload()
.
JNIEnv
is basically a wrapper for internal VM methods. Not too interesting. The usually way to invoke a JVM in C/C++ code is to first call JNI_CreateJavaVM to initialize a JavaVM
and a JNIEnv
struct and then use these two structs to invoke other operations. The former is global for this JNI call and the latter is initialized just for the current thread, which is the main thread.
When you later create a new thread in your C/C++ code, and want that new thread to a Java thread too. You'd need to first declare en empty JNIEnv
and call AttachCurrentThread
to initialize it. AttachCurrentThread
will first look up the thread table to check if there already is a JavaThread obeject that represents this calling native thread. If yes, it sets the JNIEnv
parameter to this JavaThread's jni_environment()
and return JNI_OK
. If no, it creates a new JavaThread object to represent this calling thread, and then sets the JNIEnv
parameter to this JavaThread's jni_environment()
and return JNI_OK
.
GetEnv(void **penv, jint version)
just gets the JavaThread object from the thread table, and do *(JNIEnv**)penv = javathread->jni_environment()
. If the native thread has not yet attached first but calls GetEnv()
, the vm returns JNI_EDETACHED
(Yes it is JNI_EDETACHED
instead of JNI_DETACHED
).
DetachCurrentThread()
gets the JavaThread object for the calling native thread, and then calls exit()
method on the JavaThread obejct.
This code gives an example of using these attach functions:
void callback(int val) {
JNIEnv * g_env;
// double check it's all ok
int getEnvStat = g_vm->GetEnv((void **)&g_env, JNI_VERSION_1_6);
if (getEnvStat == JNI_EDETACHED) {
std::cout << "GetEnv: not attached" << std::endl;
if (g_vm->AttachCurrentThread((void **) &g_env, NULL) != 0) {
std::cout << "Failed to attach" << std::endl;
}
} else if (getEnvStat == JNI_OK) {
//
} else if (getEnvStat == JNI_EVERSION) {
std::cout << "GetEnv: version not supported" << std::endl;
}
g_env->CallVoidMethod(g_obj, g_mid, val);
if (g_env->ExceptionCheck()) {
g_env->ExceptionDescribe();
}
g_vm->DetachCurrentThread();
}
You may want to reference the thread model part as well. For more on JNI refer to Oracle's documentations.
Note that in current HotSpot JNI implementation, the "thread-local data" stored in JNIEnv
are actually all "reserved" fields and are not being used, meaning that every thread actually has the same JNIEnv
.