Page for Step 2 of Java Integration

The second step is to learn the JNI interface in Java, and do some experiments. The experiments are to show how Java's JVM reacts to having the program-counter and stack of its main thread swapped out and replaced by a proto-runtime created virtual processor.

The experiment is to figure out whether Java's JVM is compatible with the proto-runtime mechanism of swapping out the program-counter and stack, together with a way to communicate a Java function to the proto-runtime system, such that a new virtual processor is born running that Java function.

The JNI has mechanisms to support this -- it has ways for C functions to invoke Java functions -- so this experiment will be mostly about following the JNI tutorials and getting that working.

some links:

Inside the JNI call, use the proto-runtime code to create a new VP, and set the "birth" function to be a special C function that executes a call-back into the JVM. A call-back tells the JVM to run the call-back function. Google for a JNI tutorial that shows how to do call-backs. The tutorial should show how, starting inside Java, a call is made that passes a pointer to a Java function through a JNI call over to C code. Then it should show how the C code can use a special JNI call to force the JVM to execute the Java function that was passed as a pointer.

In the experiment, get this working normally, following the tutorial. That establishes a working baseline. Then, given that, next, add a twist. The twist is that the C function doesn't just straight-away invoke the call-back, but rather it first uses proto-runtime code to create a new VP, sets the birth function of that VP to a special C function. The special C function is a stub, that takes the call-back pointer, then performs the JNI call that invokes the call-back. To make this work, a parameter struct will have to be made, to pass to the new VP. The first field is the pointer to the call-back function, the second field is the pointer to the birth params that the call-back will take as input. The special birth C function will extract the two fields and pass them to the JNI call that causes the call-back to happen.

So, here are the steps of the run of this experiment: 1) In Java, invoke a native method, and pass it 2 things: i) a pointer to a Java function, ii) parameters to give to that Java function 2) inside the native method, C code packages up the pointer to the Java function, and the parameters for it, into a C struct. 3) the native method creates a new VP, and assigns a stub C function as the birth function and passes the struct created in step 2 as the parameter to the birth function 4) the native method uses the proto-runtime assembly-language primitive call to switch the processor over to the VP that was created in step 3

This establishes that a native method can, indeed, switch the program counter and stack, and then invoke the JVM to execute a Java function.

However, it does not give evidence of whether there will be problems with garbage collection or other JVM internals, caused by the change in stack. As such, some method must be devised to exercise garbage collection, and exercise the JVM, from within the call-back. Perhaps make the call-back the "main" of a complex application, like a web-server or something.. then let that run for a while, to see if anything bad happens.

Some things to pay special attention to: OS thread related things, and garbage collection related things. In particular, the JVM has a different JNIEnv for each OS thread, and any references to Java objects that are stored inside structures allocated to the C heap must be registered with the JVM, so that the garbage collector knows that the native code holds a reference.

Things that could be pitfalls to look out for:

  • "Using the wrong JNIEnv" -- there is one for each thread in http://www.ibm.com/developerworks/library/j-jni/#wrong
  • "Using global references incorrectly" the garbage collector has to be aware of references held in native code http://www.ibm.com/developerworks/library/j-jni/#global
  • the fact that the JVM may change references dynamically, possibly causing race condition (one thread changes in middle of another accessing) http://www.artima.com/insidejvm/ed2/jvm6.html
  • Each JVM has its own heap, which is shared by all threads of an application.. so if multiple JVMs are created by the same C application, and a reference to object data on the heap of one JVM is shared with another JVM, will it get an access violation when it attempts to access that data? For sure garbage collection will not know that a reference to an object on the heap is held inside a foreign JVM's heap..
  • Switching between Java stack and native C stack -- Q:how does JNI decide which stack pointer the native call gets? Is JVM internal stuff on the stack that the native method gets? When the native method does a callback, does that JVM internal stuff need to still be on the stack?: http://www.artima.com/insidejvm/ed2/jvm9.html

Experience Gained as read about JVM and JNI

Stack Issues

http://www.artima.com/insidejvm/ed2/jvm2.html shows what's inside the JVM -- this is part of the specification, so all JVM implementations have to conform.. notice the separate Java stack vs native stack.. the Java stack is what the byte codes use. It is contained inside the JVM and is managed by the JVM. But the "native" stack is what the internal JVM C++ code uses. At the point of a JNI call, the JVM internal code just makes a normal function call to the native function. So the native function has the JVM's internal call-stack above it. Hence, return from the native method goes back to the stack frame of the JVM's internal C++ code that implements the JNI functionality. That, then resumes the JVM, which returns to the Java stack frame that made the JNI call.

http://www.artima.com/insidejvm/ed2/jvm8.html talks about the Java stack..

http://www.artima.com/insidejvm/ed2/jvm9.html talks about the native stack, and the interaction between the two stacks.. The depiction of the interaction indicates that each proto-runtime VP will need to register itself with the JVM. Otherwise, when the native stack switches, the Java stack will not, and the Java stack will get corrupted (it will be like the cactus stack issue in Cilk).. After registering, then the native stack will have state on it that JNI put there just before calling the C function. That JNI state will hold which Java stack the JNI call was made from. So, when suspend the native thread, that JNI information on the suspended stack will sit there -- and when switch the native stack back, the return from the JNI call will use the saved JNI state to resume back into the Java stack that the JNI call was made from.. so that stack part, at least, should work..)

It looks like there's a reasonable chance that the JVM's state at the point it switches to the native code can be safely suspended.. The JVM _might_ have some things in progress that, if they are suspended, and other such JVM management stuff happens, then the suspended JVM stuff is resumed, that the out-of-order completion might corrupt something.. but it's not clear, so far..

Experience Gained from looking at JVM implementation

Unfortunately, the JVM creates a native OS thread for every Java thread, and ties the native thread's stack to the internal Java stack. What this means is that the only way to create the Java side of a VP's stack is to create a Java thread object, which in turn causes an OS thread to be created.. This is a bad thing because the OS will bog down as more and more OS threads get created, in addition to the overhead of the creation time taken by the OS. So, it will limit programs to only create a small number of threads.

The most important issue, which must be avoided, is thread thrashing by the OS.. we don't want the OS to ever unblock any of the threads that have been created as part of the VP creation process.. Soooo.. looking at doing something like having such a Java thread attempt to acquire a lock that is never released.. that way, all the OS threads attached to proto-runtime VPs will remain permanently blocked, and never be swapped in by the OS.

Back to main Java Integration page