JNI
Overview
This section describes how Jikes RVM interfaces to native code. There are three major aspects of this support:
- JNI Functions: This is the mechanism for transitioning from native code into Java code. Jikes RVM implements the 1.1 through 1.4 JNI specifications.
- Native methods: This is the mechanism for transitioning from Java code to native code. In addition to the normal mechanism used to invoke a native method, Jikes RVM also supports a more restricted syscall mechanism that is used internally by low-level VM code to invoke native code.
- Integration with m-to-n threading: Attempting to get Jikes RVM's cooperative m-to-n threading model to work nicely (at all) with native code is a major challenge. We have gone through several major redesigns of the JNI support code and RVM thread system in the process. This is still a work in progress.Each of these aspects is discussed in more detail in the following sections.
JNI Functions
All of the 1.1 through 1.4 JNIEnv interface functions are implemented.
The functions are defined in the class JNIFunctions. Methods of this class are compiled with special prologues/epilogues that translate from native calling conventions to Java calling conventions and handle other details of the transition related to m-to-n threading. Currently the optimizing compiler does not support these specialized prologue/epilogue sequences so all methods in this class are baseline compiled. The prologue/epilogue sequences are actually generated by the platform-specific JNICompiler.
Invoking Native Methods
There are two mechanisms whereby RVM may transition from Java code to native code.
The first mechanism is when RVM calls a method of the class SysCall. The native methods thus invoked are defined in one of the C and C++ files of the JikesRVM executable. These native methods are non-blocking system calls or C library services. To implement a syscall, the RVM compilers generate a call sequence consistent with the platform's underlying calling convention. A syscall is not a GC-safe point, so syscalls may modify the Java heap (eg. memcpy()). For more details on the mechanics of adding a new syscall to the system, see the header comments of SysCall.java. Note again that the syscall methods are NOT JNI methods, but an independent (more efficient) interface that is specific to Jikes RVM.
The second mechanism is JNI. Naturally, the user writes JNI code using the JNI interface. RVM implements a call to a native method by using the platform-specific JNICompiler to generate a stub routine that manages the transition between Java bytecode and native code. A JNI call is a GC-safe point, since JNI code cannot freely modify the Java heap.
Interactions with m-to-n Threading
See the Thread Management subsection for more details on the thread system and m-to-n threading in Jikes RVM.
There are two ways to execute native code: syscalls and JNI. A Java thread that calls native code by either mechanism will never be preempted by Jikes RVM. As far as Jikes RVM is concerned, a Java thread that enters native code has exclusive access to the underlying Processor (pthread) until it returns to Java. Of course the OS may preempt the underlying pthread; this falls beyond Jikes RVM's control.
Some activities (eg. GC) require all threads currently running Java code to halt. So what happens when one Java thread forces a GC while another Java thread is executing native code?
If the native code is a syscall, then the VM stalls until the native code returns. Thus, all syscalls should be non-blocking operations that return fairly soon. Note that a syscall is not a GC-safe point.
On Linux/x86, Jikes RVM "hijacks" certain blocking system calls and reflects them back into the VM. The VM then uses nonblocking equivalents. This handles many of the common cases of blocking native code without requiring the full complexity of the timer-based preemption mechanism that we used to use on AIX. A complete solution would consist of implementing both mechanisms on both platforms. We hope to do this in the future.
We got GNU Classpath's (JNI-based) AWT support to work by adding code to Classpath that tells GTk (the windowing toolkit Classpath uses) to use Jikes RVM's Java threading primitives instead of the pthread-based ones that it uses by default. Jikes RVM automatically tells Classpath to do this by setting the Java system property gnu.classpath.awt.gtk.portable.native.sync at boot time. If your native code uses the glib threading primitives, as GTk does, then this will work for you, too.
Implementation Details
Supporting the combination of blocking native code and m-to-n threading is inherently complicated. Unfortunately the Jikes RVM implementation is further complicated by the fact that too much of the control logic for transitions between C and Java code is embedded in the low-level, platform-specific JNICompiler classes. As a result, the code is hard to maintain and the JNI implementations on different platforms tend to diverge.
We have some ideas for a redesign that would enable more of the control logic to be embodied in shared Java code, but there are a few minor issues to be worked out. Hopefully this will happen eventually.
Missing Features
- Native Libraries: JNI 1.2 requires that the VM specially treat native libraries that contain exported functions named JNI_OnLoad and JNI_OnUnload. Only JNI_OnLoad is currently implemented.
- JNICompiler: The only known deficiency in JNICompiler is that the prologue and epilogues only handle passing local references to functions that expect a jobject; they will not properly handle a jweak or a regular global reference. This would be fairly easy to implement.
- JavaVM interface: The JavaVM interface has GetEnv fully implemented and AttachCurrentThread partly implemented, but DestroyJavaVM, DetachCurrentThread, and AttachCurrentThreadAsDaemon are just stubbed out and return error codes.
- Directly-Exported Invocation Interface Functions: These functions (GetDefaultJavaVMInitArgs, JNI_CreateJavaVM, and JNI_GetCreatedJavaVMs) are not implemented. This is because we do not provide a virtual machine library that can be linked against, nor do we support native applications that launch and use an embedded Java VM. There is no inherent reason why this could not be done, but we have not done so yet.
Things JNI Can't Handle
- atexit routines: Calling JNI code via a routine run at exit time means calling back into a VM that has been shutdown. This will cause the Jikes RVM to freeze on Intel architectures.
Contributions of any of the missing functionality described here (and associated tests) would be greatly appreciated.