Overview
A VM is created and destroyed by calling standard JNI interfaces JavaVM*
and JNIEnv*
. Both of them are just an array of pointers pointing to a set of functions. All the VM data structure will are created in the native memory globally, but the JNI program has no direct access to these data structures, they only operate through the interfaces.
JavaVM
- operate on the VM, such as creation and destroying of the JVM.
JNIEnv
- operate on VM internal objects, classes and methods.
The launcher invokes the JVM in such a fashion:
#include <jni.h> /* where everything is defined */
...
JavaVM *jvm; /* denotes a Java VM */
JNIEnv *env; /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
* pointer in env */
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();
Life Cycle of a VM
1 2 3
[native code] ==> [vm code] ==> [interpreter code] --> [compiled code]
--> [native/jni code]
4
[interpreter code] ==> [vm code] ==> ...
4
[compiled code] ==> [vm code] ==> ...
The native thread calls
JNI_CreateJavaVM()
, execution is then transferred to vm code.JNI_CreateJavaVM
eventually callsThreads::create_vm()
, which initializes some global structures, creates a JavaThread calledmain_thread
, which is the thread that actually executes Java code from the entry point. The calling native thread is also attached to this main JavaThread. It then creates theVM thread
and starts it. After starting, the vm thread enters a forever loop that waits on the conditions that VM operations trigger, such as GC.When the
main_thread
starts executing, it interprets the entry function, which might invoke other functions or creates a new JavaThread. The intereperting chain thus goes on.Some methods or loops might be JIT-compiled during the interpreting. If so the execution is transferred to the compiled code in the code cache. Some Java method could also be implemented via JNI, namely marked as
native
, such as some java.lang.Thread methods. In such case, the execution moves to native code section.When the Java code is executing in the interpreter or in the compiled code, some VM operations might be triggered. For example, if no more object allocation request can be met, a minor GC is triggered, which is a VM operation, so the vm thread takes over the execution.
JNI code's situation is slightly more complicated. They do not change VM's state as long as they do not interact with the VM via JNI interfaces. That's why some might say that "JNI code always runs at safepoints, it calls JNI API to leave the safepoint for the duration of the API call, and then enter a safepoint again before returning to the calling JNI code."