What does a JVM have to do when calling a native method?
Calling a JNI method from Java is rather expensive comparing to a simple C function call. HotSpot typically performs most of the following steps to invoke a JNI method:
- Create a stack frame.
- Move arguments to proper register or stack locations according to ABI.
- Wrap object references to JNI handles.
- Obtain
JNIEnv*
andjclass
for static methods and pass them as additional arguments. - Check if should call
method_entry
trace function. - Lock an object monitor if the method is
synchronized
. - Check if the native function is linked already. Function lookup and linking is performed lazily.
- Switch thread from
in_java
toin_native
state. - Call the native function
- Check if safepoint is needed.
- Return thread to
in_java
state. - Unlock monitor if locked.
- Notify
method_exit
. - Unwrap object result and reset JNI handles block.
- Handle JNI exceptions.
- Remove the stack frame.
The source code for this procedure can be found at SharedRuntime::generate_native_wrapper.
As you can see, an overhead may be significant. But in many cases most of the above steps are not necessary. For example, if a native method just performs some encoding/decoding on a byte array and does not throw any exceptions nor it calls other JNI functions. For these cases HotSpot has a non-standard (and not known) convention called Critical Natives
, discussed here.