Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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:

  1. What is the exact meaning of extended period of time?

  2. 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?

  3. Is GetPrimitiveArrayCritical thread-safe?

  4. Is there anything I should know when using GetPrimitiveArrayCritical instead of GetArrayRegion?

like image 728
keelar Avatar asked Apr 24 '14 01:04

keelar


People also ask

How does JNI work?

It defines a way for the bytecode that Android compiles from managed code (written in the Java or Kotlin programming languages) to interact with native code (written in C/C++). JNI is vendor-neutral, has support for loading code from dynamic shared libraries, and while cumbersome at times is reasonably efficient.

Does JNI use reflection?

Each method that can be called via JNI has a reflection metadata object. The address of this object is used as the method's jmethodID . The metadata object contains the addresses of all of the method's generated call wrappers.

How will you create a native method header?

Generate a header file for the native method using javah with the native interface flag -jni . Once you've generated the header file you have the formal signature for your native method. Write the implementation of the native method in the programming language of your choice, such as C or C++.


3 Answers

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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).
  5. You may nest Get*Critical and Release*Critical methods, and they are thread-safe.
  6. 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.
  7. 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.
like image 53
Aleksandr Dubinsky Avatar answered Oct 12 '22 14:10

Aleksandr Dubinsky


The key thing to understand here is that you are acquiring a Critical Section (e.g. a lock) on this piece of memory.

  1. extended period of time is intended to indicate that once you hold this lock, you're blocking the JVM from doing it's usual things. So you should do whatever processing you need to do as quickly as possible. You certainly don't want to go off an do operations that might block, for example, as you'd bring the system to a complete halt.

  2. You might be able to get away with it as I suspect the main thing this lock does is prevent garbage collection, but the documentation is quite clear that it's not supported behaviour to call other JNI functions. So you might find your code works in one version of the JVM and not others.

  3. As this is acquiring a lock (Critical Section), yes, it's thread-safe. Because it's a lock, you shouldn't hold it for too long (see 1).

  4. GetArrayRegion will always give you a copy, GetPrimitiveArrayCritical may give you a copy or may give you a direct pointer. The reason it's not certain is it gives the JVM implementer more future flexibility to avoid direct pointers if they will affect general VM performance too much (i.e. there might be too much impact on some garbage collectors to make it worthwhile allowing locking).

like image 44
Jim Moores Avatar answered Oct 12 '22 13:10

Jim Moores


GetByteArrayElements method can not guarantee that your program use reference or copy. JNI return isCopy flag for state it copied object or pinned it(pin means reference). If you dont want to copy it never, you havent to use GetArrayElements methods, because it always returns copy(JVM decides copy or not and probably copy prefered because copy eases burden of Garbage Collector). I tried it and I saw that my ram increased when sent a big array. You can also see that at below link:

IBM copy and pin (look at copy and pin subject from treeview)

As document says,GetPrimitiveArrayCritical returns the direct heap address of a Java array, disabling garbage collection until the corresponding ReleasePrimitiveArrayCritical is called. So you must use that GetPrimitiveArrayCritical, if you dont want to copy(u need that when you have a big array).

For understanding GetArrayRegion, you can read below link:

GetArrayRegion

I suppose that if you want to get all of array, use GetPrimitiveArrayCritical, if you want to get a piece of array, use GetArrayRegion.

like image 29
Mustafa Kemal Avatar answered Oct 12 '22 14:10

Mustafa Kemal