This project has been archived because the Java Memory API has been stable for some time now (even though this library was originally intended as a tool for creating backports), and I still don't have the necessary hardware to complete it.
Direct native method calls without JNI overhead (HotSpot only)
Project is currently in prerelease stage (tested only on x86_64 Windows and Linux)
Required: java 8+.
ARM support will be added later (because I don't have a proper hardware and emulators are useless for performace-related development).
Any contribution is welcome.
This small library allows you to directly invoke native methods from JIT-compiled Java code by replacing the instruction that calls JNI overhead from a JIT-compiled Java method directly to the call of the target function. You cannot use JNIEnv functions or oops (Java objects) in these methods. Only primitive types are allowed in function arguments and return values.
Unlike JNICritical, these methods do not transfer thread to the native state (which also means that they lock GC) and work faster, but they do not allow arrays to be passed as arguments.
This is still intended for short method calls.
Invocation native method (takes 1 int argument and returns it):
JNI: 6,767 ± 0,267 ns/op
JNIDirect: 2,522 ± 1,051 ns/op
Long pointer dereference ( getLong(ptr) ):
JNI: 7,441 ± 0,855 ns/op
JNICritical: 8,861 ± 4,465 ns/op
JNIDirect: 2,237 ± 0,135 ns/op
Unsafe (HotSpot intrinsic): 3,220 ± 1,100 ns/op
JNIDirectIntrinsic (runtime-generated): 1,438 ± 0,062 ns/op
JNIDirectIntrinsic (for Java 23): 1,638 ± 0,321 ns/op (no JavaCritical optimization beacuse it doesn't work there)
Foreign API (Java 23, inline mov): 0,894 ± 0,061 ns/op
Measurements were performed on Java 8, unless otherwise noted.
Platform: Windows 10 x64, Intel(R) Core(TM) i5-9300H CPU @ 2.40GHz
Java native methods must meet the following requirements:
- Static and not synchronized
- Arguments and return values may only be primitives
- Complete in a short time
To make a native method direct:
- Move the implementation of the method into a separate
function without a fixed name.
Remove the JNIEnv and jclass arguments.
If the function takes any other arguments, put macroJNIDirectArgsbefore them. - Declare a non-const global variable
void*with the same name as your new function and add postfix_bridge. Set value to NULL.
This pointer will be used to store the address of the generated machine code which jumps from a Java method to your function on 64-bit architectures (it is necessary because HotSpot JIT uses a relative call to invoke JNI overhead and dynamically loaded functions are usually located too far and must be called by an absolute address which won't fit in 32 bits on 64-bit architectures). - Make the old JNI function (function with
JNIEXPORT & JNICALLwhich you used before) invoke this new function.
If function takes any args, put macroJNIDirectAInvokebefore it.
This is a fallback for non-supported JVM's and non-JIT-compiled methods. - Add call to
JNIDirectInitin the old JNI function, before invocaking a new one:
JNIDirectInit((void*)&[your new function name],&[your new function name]_bridge,[function arguments amount]);- For better performance you may put it in a JavaCritical function to avoid burdening non-JIT-compiled methods with attempts to load a JNIDirect call. But make sure that the target JVM supports JNICritical, if it supports JNIDirect.
Note: if you use C++, all these functions have to be in extern "C" {} block.
// JNIDirectTest.java
package test;
public class JNIDirectTest {
public static native long myfunc(int arg);
}/* library.c */
jlong myDirectFunc(JNIDirectArgs jint arg) {
// func implementation
}
// it will be points to runtime-generated function that calls myDirectFunc (only on 64 bit architecture, 32 bit doesn't need this)
void* myDirectFunc_bridge = NULL;
JNIEXPORT jlong JNICALL Java_test_JNIDirectTest_myfunc(JNIEnv* env, jclass caller, jint arg) {
JNIDirectInit((void*)&myDirectFunc,&myDirectFunc_bridge,1);
return myDirectFunc(JNIDirectAInvoke arg,1); // fallback for other JVMs and non-jit compiled methods
}There are minimal security checks for better performance, so you generally have to be very careful.
This library does not provide ready-made JNI functions or any Java API.