What's the trade-off between using GetPrimitiveArrayCritical and Get<PrimitiveType>ArrayRegion?

arrays, c++, java, java-native-interface, multithreading

Solution

`GetPrimitiveArrayCritical` will block all existing garbage collectors. (The experimental Shenandoah collector will often not block.) Blocking the garbage collector will block all object allocation (once the garbage piles up).

Therefore, the rules of using `GetPrimitiveArrayCritical` are as follows:

- Do not call any JNI functions. The documentation in various parts does not stress this point sufficiently, but it is a rule you must obey. The reason is that JNI functions may allocate memory, particularly local references. Because it is not documented which JNI functions allocate memory, or how much, you cannot call any of of them. Supposedly, the function EnsureLocalCapacity can pre-allocate local references to work around this issue, but nobody documented how to use it. Don't call JNI functions except GetPrimitiveArrayCritical, GetStringCritical, ReleasePrimitiveArrayCritical, and ReleaseStringCritical inside a critical region, or you will deadlock.

- Do not, in any other way, block on code that may need to allocate memory from the heap. This mostly forbids blocking on Java code running in the same VM. Conceivably (but I can't say for sure), you can block on Java code that does not allocate.

- You are allowed to call JNI functions from other threads, as long as you don't block waiting on those threads. The threads in which you call JNI functions may stall. See next point.

- Spending too much time in the critical region will stall other threads. Depending on how many threads you are running, and their allocation rate, the amount of time you can spend in the critical region can vary. In a single-threaded application, or in a multi-threaded one that does very few allocations, you can safely spend indefinite time in the critical region. In other situations, however, you will stall threads so much that the performance benefits of `GetPrimitiveArrayCritical` will be completely negated. Stalling, however, is safe from a correctness point of view (as opposed to deadlocking).

- You may nest `Get*Critical` and `Release*Critical` methods, and they are thread-safe.

- Check the return value for `null` and set `mode` correctly, because `Get*Critical` methods are allowed to fail and/or make copies, just like `Get*ArrayElements`.

- If you are writing a library and are considering using `GetPrimitiveArrayCritical`, create a runtime option to use `Get*ArrayElements` instead. Even if you do not experience the stalling that arises from GetPrimitiveArrayCritical, your users might.

The Java flag `-Xcheck:jni` will warn you if you call JNI functions inside a critical region. Ignore the documentation that says it is sometimes ok to call JNI functions inside a critical region. It isn't.

The Java 8 flags `-XX:+PrintJNIGCStalls -XX:+PrintGCDetails` will print useful log messages about stalled allocations and collections. The messages to look for can be gleaned from src/share/vm/memory/gcLocker.cpp

In Java 9, logging has changed. Turn on logging for gc and jni. The messages to look for can be gleaned from src/share/vm/gc/shared/gcLocker.cpp

More information:

- https://shipilev.net/jvm-anatomy-park/9-jni-critical-gclocker/

- The publicly-available information on this is scarce. I am currently petitioning responsible people to clean up the JNI specification or otherwise clear up the confusion.

Problem

When bridging c++ and Java using JNI, we always want to avoid unnecessary copying. I found `GetPrimitiveArrayCritical` might give us a high chance not copying an array. But I don't fully understand its restriction documented here: 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.) These restrictions make it more likely that the native code will obtain an uncopied version of the array, even if the VM does not support pinning. My questions are: What is the exact meaning of extended period of time? So does that mean we can safely call other JNI functions or system call that will never cause the current thread to block and wait for another Java thread? Is GetPrimitiveArrayCritical thread-safe? Is there anything I should know when using GetPrimitiveArrayCritical instead of GetArrayRegion?

Original source