Java JNI Optimization Guide: Improve Native Code Performance with Examples
We’ll walk through what JNI (Java Native Interface) is, why it’s both powerful and tricky, and how to use it efficiently. Along the way, I’ll show you some bits of code examples to demonstrate both bad and optimized JNI usage.
1. What is JNI?
JNI (Java Native Interface) is the bridge between Java and native code (usually C or C++).
It allows Java applications to call into high-performance native libraries, GPU APIs, or OS-level functions that aren’t directly accessible from Java.
- Pros
- Access to high-performance native libraries
- Direct access to low-level system resources
- Better raw performance in compute-intensive tasks
- Cons
- High overhead: every call crosses the Java and Native boundary
- Manual memory management: risk of leaks or crashes
- Poor portability: you may need to recompile for each platform
- Harder debugging: native crashes are less transparent
Rule of thumb: don’t overuse JNI. Use it only where performance really matters.
2. Five Key Ideas for Optimizing JNI Calls
1. Reduce the number of JNI calls
- Instead of calling native code for each single element, pass the entire array and handle it in one call
public class JNIDemo {
public static native int square(int x); // bad examples
public static native int sumOfSquares(int[] arr); // good examples
}
2. Avoid unnecessary data copying
- Use
DirectByteBufferfor zero-copy memory access - Or use
GetPrimitiveArrayCritical/ReleasePrimitiveArrayCriticalcarefully
public class JNIDemoBuffer {
public static native long sumOfSquaresBuffer(ByteBuffer buf, int len);
public static void main(String[] args) {
// Allocate off-heap memory (not in Java heap)
ByteBuffer buf = ByteBuffer.allocateDirect(n * 4);
for (int i = 0; i < n; i++) buf.putInt(i);
buf.flip();
long sum = sumOfSquaresBuffer(buf, n);
}
}
3. Optimize string handling
- Avoid frequent
GetStringUTFChars(creates copies) - Prefer
GetStringUTFRegionfor substring access
JNIEXPORT void JNICALL Java_JNIDemoString_printSubstring
(JNIEnv *env, jclass cls, jstring input, jint start, jint len) {
char buf[64];
// Directly copy the substring [start, start+len) into buf
(*env)->GetStringUTFRegion(env, input, start, len, buf);
buf[len] = '\0'; // Null-terminate the string
}
4. Minimize temporary object allocations
- Create objects on the Java side when possible
- Cache
methodID/fieldIDinstead of looking them up repeatedly
5. Cache and static binding
- Use
RegisterNativesto pre-register methods - Use
static nativemethods to skip virtual dispatch
public class JNIDemoCache {
// Use static native method to avoid virtual dispatch
public static native void callCached();
public void sayHello() {
System.out.println("Hello from Java instance method!");
}
}
// Global variables to cache methodID and class reference
static jmethodID mid_sayHello = NULL;
static jclass cls_cache = NULL;
JNIEXPORT void JNICALL Java_JNIDemoCache_callCached
(JNIEnv *env, jclass cls) {
// Allocate an object without calling its constructor
jobject obj = (*env)->AllocObject(env, cls_cache);
// Call the cached Java method "sayHello"
(*env)->CallVoidMethod(env, obj, mid_sayHello);
}
// Cache methodID and class reference when the library is loaded
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
(*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_8);
// Find the Java class and create a global reference
jclass localCls = (*env)->FindClass(env, "JNIDemoCache");
cls_cache = (*env)->NewGlobalRef(env, localCls);
// Cache the methodID of "sayHello"
mid_sayHello = (*env)->GetMethodID(env, cls_cache, "sayHello", "()V");
// Return the JNI version
return JNI_VERSION_1_8;
}
3. What’s Next?
JNI is powerful but tricky. Modern JVMs are introducing safer alternatives.
- Project Panama
- JEP 454: Foreign Function & Memory API (stable since Java 22)
These new APIs aim to replace JNI with a safer, more ergonomic, and more performant way to call native code.
Related Resources
- Java Native Interface (JNI) Specification
- JEP 454: Foreign Function & Memory API
- Project Panama: Interfacing with Native Code
- Java Performance Tuning Guide
- Related post: Java 21 Feature: Virtual Threads Explained with Examples - Project Loom Guide
- Related post: Java 21 Virtual Threads vs FixedThreadPool: Performance Comparison with 10,000 Tasks