Search code examples
javac++java-native-interfacenative

does NewDirectByteBuffer create a copy in native code


I am creating two arrays in c++ which will be read in java side:

env->NewDirectByteBuffer
env->NewByteArray

Do these functions copy the buffer I send it? Do I need to create the buffer on the heap in the c++ side or is it ok to create it on the stack because the jvm will copy it?

for example will this code run ok:

std::string stam = "12345";
const char *buff = stam.c_str();
jobject directBuff = env->NewDirectByteBuffer((void*)buff, (jlong) stam.length() );

Another example:

std::string md5 "12345";    
jbyteArray md5ByteArray = env->NewByteArray((jsize) (md5.length()));
env->SetByteArrayRegion(md5ByteArray, 0, (jsize) (md5.length()), (jbyte*)    
 md5.c_str());

string is created on the stack. Will this code always work or do I need to create those strings on the heap and be responsible to delete it after java finishes using it


Solution

  • Your use of DirectByteBuffer will almost certainly fail in spectacular, core-dumping, and unpredictable ways. And its behavior may vary between JVM implementations and operating systems. The problem is that your direct memory must remain valid for the lifetime of the DirectByteBuffer. Since your string is on the stack, it will go out of scope rather quickly. Meanwhile the Java code may or may not continue to use the DirectByteBuffer, depending on what it is. Are you writing the Java code too? Can you guarantee that its use of the DirectByteBuffer will be complete before the string goes out of scope?

    Even if you can guarantee that, realize that Java's GC is non-deterministic. It is all too easy to think that your DirectByteBuffer isn't being used any more, but meanwhile it is wandering around in unreclaimed objects, which eventually get hoovered up by the GC, which may call some finalize() method that accidentally touches the DirectByteBuffer, and -- kablooey! In practice, it is very difficult to make these guarantees except for blocks of "shared memory" that never go away for the life of your application.

    NewDirectByteBuffer is also not that fast (at least not in Windows), despite the intuitive assumption that performance is what it is all about. I've found experimentally that it is faster to copy 1000 bytes than it is to create a single DirectByteBuffer. It is usually much faster to have your Java pass a byte[] into the C++ and have the C++ copy bytes into it (ahem, assuming they fit). Overall, I make these recommendations:

    1. Call NewByteArray() and SetByteArrayRegion(), return the resulting jBytearray to Java and have no worries.
    2. If performance is a requirement, pass the byte[] from Java to C++ and have C++ fill it in by calling SetByteArrayRegion(). You might need two C++ calls, one to get the size and the next to get the data.
    3. If the data is huge, use NewDirectBtyeBuffer and make sure that the C++ data stays around "forever", or until you are darn certain that the DirectByteBuffer has been disposed.

    I've also read that both C++ and Java can memory-map the same file, and that this works very well for large data.