Search code examples
javajava-native-interface

Is direct buffer required for JNI / external library calls?


I want to pass the addresses of Java objects (especially byte arrays) to system calls and external functions which return within one JNI call.

The answer from OpenJDK's own JNI functions is apparently no - for instance the readBytes/writeBytes io_util used by file streams always introduce a direct buffer / heap by malloc for anything related to external calls.

But, why? I checked the code in java.nio.Bits (copyFromArray) and sun.misc.Unsafe (copyMemory), and it's very clear that the contents of Java primitive arrays can be accessed directly in ordinary C code without any special treatment (such as notifying VM for memory pinning or dealing with non-continuous blocks) as long as it's within the scope of the current JNI function. So it seems GC cannot happen when a JNI method is being invoked, but if that's true, why do readBytes/writeBytes always make a copy of data in C heap?

Anyone with experience on this? I'm not seeking official advice/recommendation from OpenJDK or Oracle. Not interested in portability or anything beyond the current implementations.


Solution

  • Pinning the array with GetPrimitiveArrayBytesCritical() is very lightweight, and was meant exactly for that purpose. But JNI spec describes explicitly what actions can be performed between Get/Release…Critical:

    After calling GetPrimitiveArrayCritical, the native code should not run for an extended period of time before it calls ReleasePrimitiveArrayCritical. We must treat the code inside this pair of functions as running in a "critical region." Inside a critical region, native code must not call other JNI functions, or any system call that may cause the current thread to block and wait for another Java thread. (For example, the current thread must not call read on a stream being written by another Java thread.)

    Also, the doc explains that a JVM might internally represent arrays in a different format, and then even these functions will return a copy. DirectByteBuffer, on the contrary, is guaranteed to be in C-compatible format.

    The side effects that your simple experiments could not account for, are exceptions thrown either in Java or in C, access from other threads, and obviously - alternative JVM. The specs were set before the 64-bit virtual memory addressing became widespread, and now there is no strong reason to change them.

    All JNI is kind of frozen for many years, because it is "good enough" on one hand, and "not important enough" on the other, and the main concern is compatibility, which is a serious challenge in itself, given the number of independent implementations.