Best practices for using the Java Native Interface(JNI)
1. Why using JNI?
- A standard Java API that enables Java code to integrate with code written in other programming languages.
- Integrate with existing legacy code to avoid a rewrite.
- Implement functionality missing in available class libraries.
- Integrate with code that’s best written in C/C++, to exploit performance or other environment-specific system characteristics.
2. Performance pitfalls
- Not caching method IDs, field IDs, and Classes
To access Java objects’ fields and invoke their methods, native code must make calls to FindClass(), GetFieldID(), GetMethodId(), and GetStaticMethodID()
. The IDs returned for a given class don’t change for the lifetime of the JVM process. But the call to get the field require significant work in the JVM. Because the IDs are the same for a given class, you should look them up once and then reuse them.
Calling a static method with JNI:
We can cache jmethodID, reducing call times from 6 to 2!
- Triggering array copies
The Java specification leaves it up to the JVM implementation whether these calls provide direct access to the arrays or return a copy of the array.
For example, if you call GetLongArrayElements()
on an array with 1,000 elements, you might cause the allocation and copy of at least 8,000 bytes (1,000 elements * 8 bytes for each long). When you then update the array’s contents with ReleaseLongArrayElements()
, another copy of 8,000 bytes might be required to update the array.
The GetTypeArrayRegion() and SetTypeArrayRegion()
methods allow you to get and update a region of an array, as opposed to the full array.
- Reaching back instead of passing parameters
- Choosing the wrong boundary between native and Java code
Ensure that data is maintained on the correct side of the Java/native boundary. If data resides on the wrong side, constant transitions will be triggered by the need of the other side to reach for that data.
- Using many local references without informing the JVM
Local references are created for any object returned by a JNI function. When a native causes the creation of a large number of local references, delete each reference when it is no longer required.
-
Using many local references without informing the JVM ref
-
使用GetPrimitiveArrayCritical比Get*ArrayElements更快,因为GetArrayRegion will always give you a copy, GetPrimitiveArrayCritical may give you a copy or may give you a direct pointer
-
GetPrimitiveArrayCritical后必须ReleasePrimitiveArrayCritical,俩之间是gc locker保护临界区,这段中间的代码,也不能通过JNI再调用其他的Java代码(因为jni call会分配内存),或者是任何其他可能造成等待某个其他java线程的事情,或者是java端分配内存。而且如果不release,会block住java的gc,会导致内存撑爆
-
3. Correctness pitfalls
- Using the wrong JNIEnv
JNIEnv is local to a thread. Only use the JNIEnv with the single thread to which it is associated.
For optimal performance, a thread should pass the JNIEnv that it received, because looking it up can require significant work.
Initializing jni env:
- Not checking for exceptions
Always check for exceptions after making JNI calls that can raise exceptions.
Example: my blog Throw Exception through JNI
- Not checking return values
Many JNI methods have a return value that indicates whether the call succeeded or not. Always check the return value from a JNI method and include code to handle errors.
- Using array methods incorrectly
Don’t forget to call ReleaseXXX() with a mode of 0 (copy back and free the memory) for each GetXXX() call.
Ensure the code does not make any JNI calls or block for any reason between calls to GetXXXCritical() and ReleaseXXXCritical().
- Using global references incorrectly
Always keep track of global references and ensure they are deleted when the object is no longer required.
When the native returns, not only has it not freed the global reference, but the application also no longer has a way to get the reference in order to free it later –> memory leak