Skip to content
This repository was archived by the owner on Jan 15, 2026. It is now read-only.

Y-Sulphuris/JNIDirect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

13 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JNIDirect


Archiving Note

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

How to use:

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:

  1. 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 macro JNIDirectArgs before them.
  2. 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).
  3. Make the old JNI function (function with JNIEXPORT & JNICALL which you used before) invoke this new function.
    If function takes any args, put macro JNIDirectAInvoke before it.
    This is a fallback for non-supported JVM's and non-JIT-compiled methods.
  4. Add call to JNIDirectInit in the old JNI function, before invocaking a new one:
    JNIDirectInit((void*)&[your new function name],&[your new function name]_bridge,[function arguments amount]);
    1. 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.

Example:

// 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.

About

Direct JNI calls without overhead (HotSpot only)

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors